CATEGORII DOCUMENTE |
Asp | Autocad | C | Dot net | Excel | Fox pro | Html | Java |
Linux | Mathcad | Photoshop | Php | Sql | Visual studio | Windows | Xml |
In Java 1.1 a dramatic change has been accomplished in the creation of the new AWT. Most of this change revolves around the new event model used in Java 1.1: as bad, awkward, and non-object-oriented as the old event model was, the new event model is possibly the most elegant I have seen. It's difficult to understand how such a bad design (the old AWT) and such a good one (the new event model) could come out of the same group. This new way of thinking about events seems to drop so easily into your mind that the issue no longer becomes an impediment; instead, it's a tool that helps you design the system. It's also essential for Java Beans, described later in the chapter.
Instead of the non-object-oriented cascaded if statements in the old AWT, the new approach designates objects as "sources" and "listeners" of events. As you will see, the use of inner classes is integral to the object-oriented nature of the new event model. In addition, events are now represented in a class hierarchy instead of a single class, and you can create your own event types.
You'll also find, if you've programmed with the old AWT, that Java 1.1 has made a number of what might seem like gratuitous name changes. For example, setSize( ) replaces resize( ). This will make sense when you learn about Java Beans, because Beans use a particular naming convention. The names had to be modified to make the standard AWT components into Beans.
Java 1.1 continues to support the old AWT to ensure backward compatibility with existing programs. Without fully admitting disaster, the online documents for Java 1.1 list all the problems involved with programming the old AWT and describe how those problems are addressed in the new AWT.
Clipboard operations are supported in 1.1, although drag-and-drop "will be supported in a future release." You can access the desktop color scheme so your Java program can fit in with the rest of the desktop. Pop-up menus are available, and there are some improvements for graphics and images. Mouseless operation is supported. There is a simple API for printing and simplified support for scrolling.
In the new event model a component can initiate ("fire") an event. Each type of event is represented by a distinct class. When an event is fired, it is received by one or more "listeners," which act on that event. Thus, the source of an event and the place where the event is handled can be separate.
Each event listener is an object of a class that implements a particular type of listener interface. So as a programmer, all you do is create a listener object and register it with the component that's firing the event. This registration is performed by calling a addXXXListener( ) method in the event-firing component, in which XXX represents the type of event listened for. You can easily know what types of events can be handled by noticing the names of the addListener methods, and if you try to listen for the wrong events you'll find out your mistake at compile time. Java Beans also uses the names of the addListener methods to determine what a Bean can do.
All of your event logic, then, will go inside a listener class. When you create a listener class, the sole restriction is that it must implement the appropriate interface. You can create a global listener class, but this is a situation in which inner classes tend to be quite useful, not only because they provide a logical grouping of your listener classes inside the UI or business logic classes they are serving, but because (as you shall see later) the fact that an inner class object keeps a handle to its parent object provides a nice way to call across class and subsystem boundaries.
A simple example will make this clear. Consider the Button2.java example from earlier in this chapter.
//: Button2New.java
// Capturing button presses
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2New extends Applet
class B1 implements ActionListener
}
class B2 implements ActionListener
}
/* The old way:
public boolean action(Event evt, Object arg)
*/
} ///:~
So you can compare the two approaches, the old code is left in as a comment. In init( ), the only change is the addition of the two lines:
b1.addActionListener(new B1());
b2.addActionListener(new B2());
addActionListener( ) tells a button which object to activate when the button is pressed. The classes B1 and B2 are inner classes that implement the interface ActionListener. This interface contains a single method actionPerformed( ) (meaning "This is the action that will be performed when the event is fired"). Note that actionPerformed( ) does not take a generic event, but rather a specific type of event, ActionEvent. So you don't need to bother testing and downcasting the argument if you want to extract specific ActionEvent information.
One of the nicest things about actionPerformed( ) is how simple it is. It's just a method that gets called. Compare it to the old action( ) method, in which you must figure out what happened and act appropriately, and also worry about calling the base class version of action( ) and return a value to indicate whether it's been handled. With the new event model you know that all the event-detection logic is taken care of so you don't have to figure that out; you just say what happens and you're done. If you're don't already prefer this approach over the old one, you will soon.
All the AWT components have been changed to include addXXXListener( ) and removeXXXListener( ) methods so
that the appropriate types of listeners can be added and removed from each
component. You'll notice that the "XXX"
in each case also represents the argument for the method, for example, addFooListener(FooListener fl). The following
table includes the associated events, listeners, methods, and the components
that support those particular events by providing the addXXXListener( ) and removeXXXListener( )
methods.
Event, listener interface and add- and remove-methods |
Components supporting this event |
ActionEvent |
Button, List, TextField, MenuItem, and its derivatives including CheckboxMenuItem, Menu, and PopupMenu |
AdjustmentEvent |
Scrollbar |
ComponentEvent |
Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField |
ContainerEvent |
Container and its derivatives, including Panel, Applet, ScrollPane, Window, Dialog, FileDialog, and Frame |
FocusEvent |
Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame Label, List, Scrollbar, TextArea, and TextField |
KeyEvent |
Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField |
MouseEvent (for
both clicks and motion) |
Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField |
MouseEvent (for
both clicks and motion) |
Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField |
WindowEvent |
Window and its derivatives, including Dialog, FileDialog, and Frame |
ItemEvent |
Checkbox, CheckboxMenuItem, Choice, List, and anything that implements the ItemSelectable interface |
TextEvent |
Anything derived from TextComponent, including TextArea and TextField |
You can see that each type of component supports only certain types of events. It's helpful to see the events supported by each component, as shown in the following table:
Component type |
Events supported by this component |
Adjustable |
AdjustmentEvent |
Applet |
ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Button |
ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Canvas |
FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Checkbox |
ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
CheckboxMenuItem |
ActionEvent, ItemEvent |
Choice |
ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Component |
FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Container |
ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Dialog |
ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
FileDialog |
ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Frame |
ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Label |
FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
List |
ActionEvent, FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent |
Menu |
ActionEvent |
MenuItem |
ActionEvent |
Panel |
ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
PopupMenu |
ActionEvent |
Scrollbar |
AdjustmentEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
ScrollPane |
ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
TextArea |
TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
TextComponent |
TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
TextField |
ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Window |
ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent |
Once you know which events a particular component supports, you don't need to look anything up to react to that event. You simply:
Take the name of the event class and remove the word "Event." Add the word "Listener" to what remains. This is the listener interface you need to implement in your inner class.
Implement the interface above and write out the methods for the events you want to capture. For example, you might be looking for mouse movements, so you write code for the mouseMoved( ) method of the MouseMotionListener interface. (You must implement the other methods, of course, but there's a shortcut for that which you'll see soon.)
Create an object of the listener class in step 2. Register it with your component with the method produced by prefixing "add" to your listener name. For example, addMouseMotionListener( ).
To finish what you need to know, here are the listener interfaces:
Listener interface |
Methods in interface |
ActionListener |
actionPerformed(ActionEvent) |
AdjustmentListener |
adjustmentValueChanged( |
ComponentListener |
componentHidden(ComponentEvent) |
ContainerListener |
componentAdded(ContainerEvent) |
FocusListener |
focusGained(FocusEvent) |
KeyListener |
keyPressed(KeyEvent) |
MouseListener |
mouseClicked(MouseEvent) |
MouseMotionListener |
mouseDragged(MouseEvent) |
WindowListener |
windowOpened(WindowEvent) |
ItemListener |
itemStateChanged(ItemEvent) |
TextListener |
textValueChanged(TextEvent) |
In the table above, you can see that some listener interfaces have only one method. These are trivial to implement since you'll implement them only when you want to write that particular method. However, the listener interfaces that have multiple methods could be less pleasant to use. For example, something you must always do when creating an application is provide a WindowListener to the Frame so that when you get the windowClosing( ) event you can call System.exit(0) to exit the application. But since WindowListener is an interface, you must implement all of the other methods even if they don't do anything. This can be annoying.
To solve the problem, each of the listener interfaces that have more than one method are provided with adapters, the names of which you can see in the table above. Each adapter provides default methods for each of the interface methods. (Alas, WindowAdapter does not have a default windowClosing( ) that calls System.exit(0).) Then all you need to do is inherit from the adapter and override only the methods you need to change. For example, the typical WindowListener you'll use looks like this:
class MyWindowListener extends WindowAdapter
}
The whole point of the adapters is to make the creation of listener classes easy.
There is a downside to adapters, however, in the form of a pitfall. Suppose you write a WindowAdapter like the one above:
class MyWindowListener extends WindowAdapter
}
This doesn't work, but it will drive you crazy trying to figure out why, since everything will compile and run fine - except that closing the window won't exit the program. Can you see the problem? It's in the name of the method: WindowClosing( ) instead of windowClosing( ). A simple slip in capitalization results in the addition of a completely new method. However, this is not the method that's called when the window is closing, so you don't get the desired results.
Often you'll want to be able to create a class that can be invoked as either a window or an applet. To accomplish this, you simply add a main( ) to your applet that builds an instance of the applet inside a Frame. As a simple example, let's look at Button2New.java modified to work as both an application and an applet:
//: Button2NewB.java
// An application and an applet
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2NewB extends Applet
class B1 implements ActionListener
}
class B2 implements ActionListener
}
// To close the application:
static class WL extends WindowAdapter
}
// A main() for the application:
public static void main(String[] args)
} ///:~
The inner class WL and the main( ) are the only two elements added to the applet, and the rest of the applet is untouched. In fact, you can usually copy and paste the WL class and main( ) into your own applets with little modification. The WL class is static so it can be easily created in main( ). (Remember that an inner class normally needs an outer class handle when it's created. Making it static eliminates this need.) You can see that in main( ), the applet is explicitly initialized and started since in this case the browser isn't available to do it for you. Of course, this doesn't provide the full behavior of the browser, which also calls stop( ) and destroy( ), but for most situations it's acceptable. If it's a problem, you can:
Make the handle applet a static member of the class (instead of a local variable of main( )), and then:
Call applet.stop( ) and applet.destroy( ) inside WindowAdapter.windowClosing( ) before you call System.exit( ).
Notice the last line:
aFrame.setVisible(true);
This is one of the changes in the Java 1.1 AWT. The show( ) method is deprecated and setVisible(true) replaces it. These sorts of seemingly capricious changes will make more sense when you learn about Java Beans later in the chapter.
This example is also modified to use a TextField rather than printing to the console or to the browser status line. One restriction in making a program that's both an applet and an application is that you must choose input and output forms that work for both situations.
There's another small new feature of the Java 1.1 AWT shown here. You no longer need to use the error-prone approach of specifying BorderLayout positions using a String. When adding an element to a BorderLayout in Java 1.1, you can say:
aFrame.add(applet, BorderLayout.CENTER);
You name the location with one of the BorderLayout constants, which can then be checked at compile-time (rather than just quietly doing the wrong thing, as with the old form). This is a definite improvement, and will be used throughout the rest of the book.
Any of the listener classes could be implemented as anonymous classes, but there's always a chance that you might want to use their functionality elsewhere. However, the window listener is used here only to close the application's window so you can safely make it an anonymous class. Then, in main( ), the line:
aFrame.addWindowListener(new WL());
will become:
aFrame.addWindowListener(
new WindowAdapter()
});
This has the advantage that it doesn't require yet another class name. You must decide for yourself whether it makes the code easier to understand or more difficult. However, for the remainder of the book an anonymous inner class will usually be used for the window listener.
An important JAR use is to optimize applet loading. In Java 1.0, people tended to try to cram all their code into a single Applet class so the client would need only a single server hit to download the applet code. Not only did this result in messy, hard to read (and maintain) programs, but the .class file was still uncompressed so downloading wasn't as fast as it could have been.
JAR files change all of that by compressing all of your .class files into a single file that is downloaded by the browser. Now you don't need to create an ugly design to minimize the number of classes you create, and the user will get a much faster download time.
Consider the example above. It looks like Button2NewB is a single class, but in fact it contains three inner classes, so that's four in all. Once you've compiled the program, you package it into a JAR file with the line:
jar cf Button2NewB.jar *.class
This assumes that the only .class files in the current directory are the ones from Button2NewB.java (otherwise you'll get extra baggage).
Now you can create an HTML page with the new archive tag to indicate the name of the JAR file, like this:
<head><title>Button2NewB Example Applet
</title></head>
<body>
<applet code='Button2NewB.class'
archive='Button2NewB.jar'
width=200 height=150>
</applet>
</body>
Everything else about applet tags in HTML files remains the same.
To see a number of examples using the new event model and to study the way a program can be converted from the old to the new event model, the following examples revisit many of the issues demonstrated in the first part of this chapter using the old event model. In addition, each program is now both an applet and an application so you can run it with or without a browser.
This is similar to TextField1.java, but it adds significant extra behavior:
//: TextNew.java
// Text fields with Java 1.1 events
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextNew extends Applet
class T1 implements TextListener
}
class T1A implements ActionListener
}
class T1K extends KeyAdapter
}
else
t1.setText(
t1.getText() +
Character.toUpperCase(
e.getKeyChar()));
t1.setCaretPosition(
t1.getText().length());
// Stop regular character from appearing:
e.consume();
}
}
class B1 implements ActionListener
}
class B2 implements ActionListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
The TextField t3 is included as a place to report when the action listener for the TextField t1 is fired. You'll see that the action listener for a TextField is fired only when you press the "enter" key.
The TextField t1 has several listeners attached to it. The T1 listener copies all text from t1 into t2 and the T1K listener forces all characters to upper case. You'll notice that the two work together, and if you add the T1K listener after you add the T1 listener, it doesn't matter: all characters will still be forced to upper case in both text fields. It would seem that keyboard events are always fired before TextComponent events, and if you want the characters in t2 to retain the original case that was typed in, you must do some extra work.
T1K has some other activities of interest. You must detect a backspace (since you're controlling everything now) and perform the deletion. The caret must be explicitly set to the end of the field; otherwise it won't behave as you expect. Finally, to prevent the original character from being handled by the default mechanism, the event must be "consumed" using the consume( ) method that exists for event objects. This tells the system to stop firing the rest of the event handlers for this particular event.
This example also quietly demonstrates one of the benefits of the design of inner classes. Note that in the inner class:
class T1 implements TextListener
}
t1 and t2 are not members of T1, and yet they're accessible without any special qualification. This is because an object of an inner class automatically captures a handle to the outer object that created it, so you can treat members and methods of the enclosing class object as if they're yours. As you can see, this is quite convenient.
The most significant change to text areas in Java 1.1 concerns scroll bars. With the TextArea constructor, you can now control whether a TextArea will have scroll bars: vertical, horizontal, both, or neither. This example modifies the earlier Java 1.0 TextArea1.java to show the Java 1.1 scrollbar constructors:
//: TextAreaNew.java
// Controlling scrollbars with the TextArea
// component in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextAreaNew extends Applet
class B1L implements ActionListener
}
class B2L implements ActionListener
}
class B3L implements ActionListener
}
class B4L implements ActionListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,725);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
You'll notice that you can control the scrollbars only at the time of construction of the TextArea. Also, even if a TextArea doesn't have a scrollbar, you can move the cursor such that scrolling will be forced. (You can see this behavior by playing with the example.)
As noted previously, check boxes and radio buttons are both created with the same class, Checkbox, but radio buttons are Checkboxes placed into a CheckboxGroup. In either case, the interesting event is ItemEvent, for which you create an ItemListener.
When dealing with a group of check boxes or radio buttons, you have a choice. You can either create a new inner class to handle the event for each different Checkbox or you can create one inner class that determines which Checkbox was clicked and register a single object of that inner class with each Checkbox object. The following example shows both approaches:
//: RadioCheckNew.java
// Radio buttons and Check Boxes in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class RadioCheckNew extends Applet ;
CheckboxGroup g = new CheckboxGroup();
Checkbox
cb4 = new Checkbox('four', g, false),
cb5 = new Checkbox('five', g, true),
cb6 = new Checkbox('six', g, false);
public void init()
cb4.addItemListener(new IL4());
cb5.addItemListener(new IL5());
cb6.addItemListener(new IL6());
add(cb4); add(cb5); add(cb6);
}
// Checking the source:
class ILCheck implements ItemListener
}
}
}
// vs. an individual class for each item:
class IL4 implements ItemListener
}
class IL5 implements ItemListener
}
class IL6 implements ItemListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
ILCheck has the advantage that it automatically adapts when you add or subtract Checkboxes. Of course, you can use this with radio buttons as well. It should be used, however, only when your logic is general enough to support this approach. Otherwise you'll end up with a cascaded if statement, a sure sign that you should revert to using independent listener classes.
Drop-down lists (Choice) in Java 1.1 also use ItemListeners to notify you when a choice has changed:
//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ChoiceNew extends Applet ;
TextField t = new TextField(100);
Choice c = new Choice();
Button b = new Button('Add items');
int count = 0;
public void init()
class CL implements ItemListener
}
class BL implements ActionListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(750,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
Nothing else here is particularly new (except that Java 1.1 has significantly fewer bugs in the UI classes).
You'll recall that one of the problems with the Java 1.0 List design is that it took extra work to make it do what you'd expect: react to a single click on one of the list elements. Java 1.1 has solved this problem:
//: ListNew.java
// Java 1.1 Lists are easier to use
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ListNew extends Applet ;
// Show 6 items, allow multiple selection:
List lst = new List(6, true);
TextArea t = new TextArea(flavors.length, 30);
Button b = new Button('test');
int count = 0;
public void init()
class LL implements ItemListener
}
class BL implements ActionListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
You can see that no extra logic is required to support a single click on a list item. You just attach a listener like you do everywhere else.
The event handling for menus does seem to benefit from the Java 1.1 event model, but Java's approach to menus is still messy and requires a lot of hand coding. The right medium for a menu seems to be a resource rather than a lot of code. Keep in mind that program-building tools will generally handle the creation of menus for you, so that will reduce the pain somewhat (as long as they will also handle the maintenance!).
In addition, you'll find the events for menus are inconsistent and can lead to confusion: MenuItems use ActionListeners, but CheckboxMenuItems use ItemListeners. The Menu objects can also support ActionListeners, but that's not usually helpful. In general, you'll attach listeners to each MenuItem or CheckboxMenuItem, but the following example (revised from the earlier version) also shows ways to combine the capture of multiple menu components into a single listener class. As you'll see, it's probably not worth the hassle to do this.
//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;
public class MenuNew extends Frame ;
TextField t = new TextField('No flavor', 30);
MenuBar mb1 = new MenuBar();
Menu f = new Menu('File');
Menu m = new Menu('Flavors');
Menu s = new Menu('Safety');
// Alternative approach:
CheckboxMenuItem[] safety = ;
MenuItem[] file = ;
// A second menu bar to swap to:
MenuBar mb2 = new MenuBar();
Menu fooBar = new Menu('fooBar');
MenuItem[] other = ;
// Initialization code:
Button b = new Button('Swap Menus');
public MenuNew()
for(int i = 0; i < safety.length; i++)
s.add(safety[i]);
f.add(s);
for(int i = 0; i < file.length; i++)
f.add(file[i]);
mb1.add(f);
mb1.add(m);
setMenuBar(mb1);
t.setEditable(false);
add(t, BorderLayout.CENTER);
// Set up the system for swapping menus:
b.addActionListener(new BL());
add(b, BorderLayout.NORTH);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
class BL implements ActionListener
}
class ML implements ActionListener else if(actionCommand.equals('Exit'))
}
}
class FL implements ActionListener
}
// Alternatively, you can create a different
// class for each different MenuItem. Then you
// Don't have to figure out which one it is:
class FooL implements ActionListener
}
class BarL implements ActionListener
}
class BazL implements ActionListener
}
class CMIL implements ItemListener
}
public static void main(String[] args)
});
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
This code is similar to the previous (Java 1.0) version, until you get to the initialization section (marked by the opening brace right after the comment "Initialization code:"). Here you can see the ItemListeners and ActionListeners attached to the various menu components.
Java 1.1 supports "menu shortcuts," so you can select a menu item using the keyboard instead of the mouse. These are quite simple; you just use the overloaded MenuItem constructor that takes as a second argument a MenuShortcut object. The constructor for MenuShortcut takes the key of interest, which magically appears on the menu item when it drops down. The example above adds Control-E to the "Exit" menu item.
You can also see the use of setActionCommand( ). This seems a bit strange because in each case the "action command" is exactly the same as the label on the menu component. Why not just use the label instead of this alternative string? The problem is internationalization. If you retarget this program to another language, you want to change only the label in the menu, and not go through the code changing all the logic that will no doubt introduce new errors. So to make this easy for code that checks the text string associated with a menu component, the "action command" can be immutable while the menu label can change. All the code works with the "action command," so it's unaffected by changes to the menu labels. Note that in this program, not all the menu components are examined for their action commands, so those that aren't don't have their action command set.
Much of the constructor is the same as before, with the exception of a couple of calls to add listeners. The bulk of the work happens in the listeners. In BL, the MenuBar swapping happens as in the previous example. In ML, the "figure out who rang" approach is taken by getting the source of the ActionEvent and casting it to a MenuItem, then getting the action command string to pass it through a cascaded if statement. Much of this is the same as before, but notice that if "Exit" is chosen, a new WindowEvent is created, passing in the handle of the enclosing class object (MenuNew.this) and creating a WINDOW_CLOSING event. This is handed to the dispatchEvent( ) method of the enclosing class object, which then ends up calling windowClosing( ) inside the window listener for the Frame (this listener is created as an anonymous inner class, inside main( )), just as if the message had been generated the "normal" way. Through this mechanism, you can dispatch any message you want in any circumstances, so it's quite powerful.
The FL listener is simple even though it's handling all the different flavors in the flavor menu. This approach is useful if you have enough simplicity in your logic, but in general, you'll want to take the approach used with FooL, BarL, and BazL, in which they are each attached to only a single menu component so no extra detection logic is necessary and you know exactly who called the listener. Even with the profusion of classes generated this way, the code inside tends to be smaller and the process is more foolproof.
This is a direct rewrite of the earlier ToeTest.java. In this version, however, everything is placed inside an inner class. Although this completely eliminates the need to keep track of the object that spawned any class, as was the case in ToeTest.java, it could be taking the concept of inner classes a bit too far. At one point, the inner classes are nested four deep! This is the kind of design in which you need to decide whether the benefit of inner classes is worth the increased complexity. In addition, when you create a non-static inner class you're tying that class to its surrounding class. Sometimes a standalone class can more easily be reused.
//: ToeTestNew.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;
import java.awt.event.*;
public class ToeTestNew extends Frame
static final int BLANK = 0;
static final int XX = 1;
static final int OO = 2;
class ToeDialog extends Dialog
});
}
class ToeButton extends Canvas
public void paint(Graphics g)
if(state == OO)
}
class ML extends MouseAdapter
else
state = (state == XX ? OO : XX);
repaint();
}
}
}
}
class BL implements ActionListener
}
public static void main(String[] args)
});
f.setSize(200,100);
f.setVisible(true);
}
} ///:~
Because statics can be at only the outer level of the class, inner classes cannot have static data or static inner classes.
Converting from FileDialogTest.java to the new event model is straightforward:
//: FileDialogNew.java
// Demonstration of File dialog boxes
import java.awt.*;
import java.awt.event.*;
public class FileDialogNew extends Frame
class OpenL implements ActionListener else
}
}
class SaveL implements ActionListener else
}
}
public static void main(String[] args)
});
f.setSize(250,110);
f.setVisible(true);
}
} ///:~
It would be nice if all the conversions were this easy, but they're usually easy enough, and your code benefits from the improved readability.
One of the benefits of the new AWT event model is flexibility. In the old model you were forced to hard code the behavior of your program, but with the new model you can add and remove event behavior with single method calls. The following example demonstrates this:
//: DynamicEvents.java
// The new Java 1.1 event model allows you to
// change event behavior dynamically. Also
// demonstrates multiple actions for an event.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class DynamicEvents extends Frame
class B implements ActionListener
}
class CountListener implements ActionListener
public void actionPerformed(ActionEvent e)
}
class B1 implements ActionListener
}
class B2 implements ActionListener
}
}
public static void main(String[] args)
});
f.setSize(300,200);
f.show();
}
} ///:~
The new twists in this example are:
There is more than one listener attached to each Button. Usually, components handle events as multicast, meaning that you can register many listeners for a single event. In the special components in which an event is handled as unicast, you'll get a TooManyListenersException.
During the execution of the program, listeners are dynamically added and removed from the Button b2. Adding is accomplished in the way you've seen before, but each component also has a removeXXXListener( ) method to remove each type of listener.
This kind of flexibility provides much greater power in your programming.
You should notice that event listeners are not guaranteed to be called in the order they are added (although most implementations do in fact work that way).
In general you'll want to design your classes so that each one does "only one thing." This is particularly important when user-interface code is concerned, since it's easy to wrap up "what you're doing" with "how you're displaying it." This kind of coupling prevents code reuse. It's much more desirable to separate your "business logic" from the GUI. This way, you can not only reuse the business logic more easily, it's also easier to reuse the GUI.
Another issue is multi-tiered systems, where the "business objects" reside on a completely separate machine. This central location of the business rules allows changes to be instantly effective for all new transactions, and is thus a compelling way to set up a system. However, these business objects can be used in many different applications and so should not be tied to any particular mode of display. They should just perform the business operations and nothing more.
The following example shows how easy it is to separate the business logic from the GUI code:
//: Separation.java
// Separating GUI logic and business objects
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class BusinessLogic
public void setModifier(int mod)
public int getModifier()
// Some business operations:
public int calculation1(int arg)
public int calculation2(int arg)
}
public class Separation extends Applet
static int getValue(TextField tf) catch(NumberFormatException e)
}
class Calc1L implements ActionListener
}
class Calc2L implements ActionListener
}
class ModL implements TextListener
}
public static void main(String[] args)
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(200,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
You can see that BusinessLogic is a straightforward class that performs its operations without even a hint that it might be used in a GUI environment. It just does its job.
Separation keeps track of all the UI details, and it talks to BusinessLogic only through its public interface. All the operations are centered around getting information back and forth through the UI and the BusinessLogic object. So Separation, in turn, just does its job. Since Separation knows only that it's talking to a BusinessLogic object (that is, it isn't highly coupled), it could be massaged into talking to other types of objects without much trouble.
Thinking in terms of separating UI from business logic also makes life easier when you're adapting legacy code to work with Java.
Inner classes, the new event model, and the fact that the old event model is still supported along with new library features that rely on old-style programming has added a new element of confusion. Now there are even more different ways for people to write unpleasant code. Unfortunately, this kind of code is showing up in books and article examples, and even in documentation and examples distributed from Sun! In this section we'll look at some misunderstandings about what you should and shouldn't do with the new AWT, and end by showing that except in extenuating circumstances you can always use listener classes (written as inner classes) to solve your event-handling needs. Since this is also the simplest and clearest approach, it should be a relief for you to learn this.
Before looking at anything else, you should know that although Java 1.1 is backward-compatible with Java 1.0 (that is, you can compile and run 1.0 programs with 1.1), you cannot mix the event models within the same program. That is, you cannot use the old-style action( ) method in the same program in which you employ listeners. This can be a problem in a larger program when you're trying to integrate old code with a new program, since you must decide whether to use the old, hard-to-maintain approach with the new program or to update the old code. This shouldn't be too much of a battle since the new approach is so superior to the old.
To give you something to compare with, here's an example showing the recommended approach. By now it should be reasonably familiar and comfortable:
//: GoodIdea.java
// The best way to design classes using the new
// Java 1.1 event model: use an inner class for
// each different event. This maximizes
// flexibility and modularity.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class GoodIdea extends Frame
public class B1L implements ActionListener
}
public class B2L implements ActionListener
}
public static void main(String[] args)
});
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
This is fairly trivial: each button has its own listener that prints something out to the console. But notice that there isn't an if statement in the entire program, or any statement that says, "I wonder what caused this event." Each piece of code is concerned with doing, not type-checking. This is the best way to write your code; not only is it easier to conceptualize, but much easier to read and maintain. Cutting and pasting to create new programs is also much easier.
The first bad idea is a common and recommended approach. This makes the main class (typically Applet or Frame, but it could be any class) implement the various listeners. Here's an example:
//: BadIdea1.java
// Some literature recommends this approach,
// but it's missing the point of the new event
// model in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea1 extends Frame
implements ActionListener, WindowListener
public void actionPerformed(ActionEvent e)
public void windowClosing(WindowEvent e)
public void windowClosed(WindowEvent e)
public void windowDeiconified(WindowEvent e)
public void windowIconified(WindowEvent e)
public void windowActivated(WindowEvent e)
public void windowDeactivated(WindowEvent e)
public void windowOpened(WindowEvent e)
public static void main(String[] args)
} ///:~
The use of this shows up in the three lines:
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
Since BadIdea1 implements ActionListener and WindowListener, these lines are certainly acceptable, and if you're still stuck in the mode of trying to make fewer classes to reduce server hits during applet loading, it seems to be a good idea. However:
Java 1.1 supports JAR files so all your files can be placed in a single compressed JAR archive that requires only one server hit. You no longer need to reduce class count for Internet efficiency.
The code above is much less modular so it's harder to grab and paste. Note that you must not only implement the various interfaces for your main class, but in actionPerformed( ) you've got to detect which action was performed using a cascaded if statement. Not only is this going backwards, away from the listener model, but you can't easily reuse the actionPerformed( ) method since it's specific to this particular application. Contrast this with GoodIdea.java, in which you can just grab one listener class and paste it in anywhere else with minimal fuss. Plus you can register multiple listener classes with a single event, allowing even more modularity in what each listener class does.
The second bad idea is to mix the two approaches: use inner listener classes, but also implement one or more listener interfaces as part of the main class. This approach has appeared without explanation in books and documentation, and I can only assume that the authors thought they must use the different approaches for different purposes. But you don't - in your programming you can probably use inner listener classes exclusively.
//: BadIdea2.java
// An improvement over BadIdea1.java, since it
// uses the WindowAdapter as an inner class
// instead of implementing all the methods of
// WindowListener, but still misses the
// valuable modularity of inner classes
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea2 extends Frame
implements ActionListener
public void actionPerformed(ActionEvent e)
class WL extends WindowAdapter
}
public static void main(String[] args)
} ///:~
Since actionPerformed( ) is still tightly coupled to the main class, it's hard to reuse that code. It's also messier and less pleasant to read than the inner class approach.
There's no reason that you have to use any of the old thinking for events in Java 1.1 - so why do it?
Another place where you'll often see variations on the old way of doing things is when creating a new type of component. Here's an example showing that here, too, the new way works:
//: GoodTechnique.java
// Your first choice when overriding components
// should be to install listeners. The code is
// much safer, more modular and maintainable.
import java.awt.*;
import java.awt.event.*;
class Display
public void show(Graphics g)
}
class EnabledPanel extends Panel
// To eliminate flicker:
public void update(Graphics g)
public void paint(Graphics g)
// Don't need to enable anything for this:
public void processEvent(AWTEvent e)
class CL implements ComponentListener
public void
componentResized(ComponentEvent e)
public void
componentHidden(ComponentEvent e)
public void componentShown(ComponentEvent e)
}
class FL implements FocusListener
public void focusLost(FocusEvent e)
}
class KL implements KeyListener
public void keyReleased(KeyEvent e)
public void keyTyped(KeyEvent e)
void showCode(KeyEvent e)
}
class ML implements MouseListener
public void mousePressed(MouseEvent e)
public void mouseReleased(MouseEvent e)
public void mouseEntered(MouseEvent e)
public void mouseExited(MouseEvent e)
void showMouse(MouseEvent e)
}
class MML implements MouseMotionListener
public void mouseMoved(MouseEvent e)
void showMouse(MouseEvent e)
}
}
class MyButton extends Button
public void paint(Graphics g)
private void drawLabel(Graphics g)
class AL implements ActionListener
}
}
public class GoodTechnique extends Frame
public static void main(String[] args)
});
f.setSize(700,700);
f.setVisible(true);
}
} ///:~
This example also demonstrates the various events that occur and displays the information about them. The class Display is a way to centralize that information display. There's an array of Strings to hold information about each type of event, and the method show( ) takes a handle to whatever Graphics object you have and writes directly on that surface. The scheme is intended to be somewhat reusable.
EnabledPanel represents the new type of component. It's a colored panel with a button at the bottom, and it captures all the events that happen over it by using inner listener classes for every single event except those in which EnabledPanel overrides processEvent( ) in the old style (notice it must also call super.processEvent( )). The only reason for using this method is that it captures every event that happens, so you can view everything that goes on. processEvent( ) does nothing more than show the string representation of each event, otherwise it would have to use a cascade of if statements to figure out what event it was. On the other hand, the inner listener classes already know precisely what event occurred. (Assuming you register them to components in which you don't need any control logic, which should be your goal.) Thus, they don't have to check anything out; they just do their stuff.
Each listener modifies the Display string associated with its particular event and calls repaint( ) so the strings get displayed. You can also see a trick that will usually eliminate flicker:
public void update(Graphics g)
You don't always need to override update( ), but if you write something that flickers, try it. The default version of update clears the background and then calls paint( ) to redraw any graphics. This clearing is usually what causes flicker but is not necessary since paint( ) redraws the entire surface.
You can see that there are a lot of listeners - however, type checking occurs for the listeners, and you can't listen for something that the component doesn't support (unlike BadTechnique.java, which you will see momentarily).
Experimenting with this program is quite educational since you learn a lot about the way that events occur in Java. For one thing, it shows a flaw in the design of most windowing systems: it's pretty hard to click and release the mouse without moving it, and the windowing system will often think you're dragging when you're actually just trying to click on something. A solution to this is to use mousePressed( ) and mouseReleased( ) instead of mouseClicked( ), and then determine whether to call your own "mouseReallyClicked( )" method based on time and about 4 pixels of mouse hysteresis.
The alternative, which you will see put forward in many published works, is to call enableEvents( ) and pass it the masks corresponding to the events you want to handle. This causes those events to be sent to the old-style methods (although they're new to Java 1.1) with names like processFocusEvent( ). You must also remember to call the base-class version. Here's what it looks like:
//: BadTechnique.java
// It's possible to override components this way,
// but the listener approach is much better, so
// why would you?
import java.awt.*;
import java.awt.event.*;
class Display
public void show(Graphics g)
}
class EnabledPanel extends Panel
// To eliminate flicker:
public void update(Graphics g)
public void paint(Graphics g)
public void processEvent(AWTEvent e)
public void
processComponentEvent(ComponentEvent e)
repaint();
// Must always remember to call the 'super'
// version of whatever you override:
super.processComponentEvent(e);
}
public void processFocusEvent(FocusEvent e)
repaint();
super.processFocusEvent(e);
}
public void processKeyEvent(KeyEvent e)
int code = e.getKeyCode();
display.evnt[Display.KEY] +=
KeyEvent.getKeyText(code);
repaint();
super.processKeyEvent(e);
}
public void processMouseEvent(MouseEvent e)
display.evnt[Display.MOUSE] +=
', x = ' + e.getX() +
', y = ' + e.getY();
repaint();
super.processMouseEvent(e);
}
public void
processMouseMotionEvent(MouseEvent e)
display.evnt[Display.MOUSE_MOVE] +=
', x = ' + e.getX() +
', y = ' + e.getY();
repaint();
super.processMouseMotionEvent(e);
}
}
class MyButton extends Button
public void paint(Graphics g)
private void drawLabel(Graphics g)
public void processActionEvent(ActionEvent e)
}
public class BadTechnique extends Frame
public void processWindowEvent(WindowEvent e)
}
public static void main(String[] args)
} ///:~
Sure, it works. But it's ugly and hard to write, read, debug, maintain, and reuse. So why bother when you can use inner listener classes?
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 728
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved