1. Trang chủ
  2. » Công Nghệ Thông Tin

Ivor Horton’s Beginning Java 2, JDK 5 Edition phần 7 ppt

150 347 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 150
Dung lượng 1,98 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Now you can modify the previous version of the Sketcherclass so that it is a listener for window events:// Sketching application import java.awt.Dimension; import java.awt.Toolkit; impor

Trang 1

// Handle window eventsprotected void processWindowEvent(WindowEvent e) {

if (e.getID() == WindowEvent.WINDOW_CLOSING) {dispose(); // Release resourcesSystem.exit(0); // Exit the program}

super.processWindowEvent(e); // Pass on the event}

private JMenuBar menuBar = new JMenuBar(); // Window menu bar// File menu items

private JMenuItem newItem, openItem, closeItem,

saveItem, saveAsItem, printItem;

// Element menu itemsprivate JRadioButtonMenuItem lineItem, rectangleItem, circleItem, // Types

curveItem, textItem;

private JCheckBoxMenuItem redItem, yellowItem, // Colors

greenItem, blueItem ; }

You add the call to enableEvents()as the last in the constructor Note that the statement that setsEXIT_ON_CLOSEas the close option for the window is commented out You could delete the statement ifyou want When you compile SketchFrameand run Sketcher again, you’ll be able to close the window asbefore, and the program will shut down gracefully However, this time it’s your method that’s doing it.How It Works

The additional import statements make the constants defined in the AWTEventclass and theWindowEventclass name available to your source file without the need to qualify them with thepackage names You call enableEvents()in the constructor with WINDOW_EVENT_MASKas the argu-ment to enable window events This enables all the window events represented by the WindowEventclass An object of this class can represent one of a number of different window events that are each iden-tified by an event ID, which is a constant defined within the WindowEventclass The event IDs for theWindowEventclass are:

WINDOW_OPENED The event that occurs the first time a window is made visible

WINDOW_CLOSING The event that occurs as a result of the close icon being selected or

Close being selected from the window’s system menu

WINDOW_CLOSED The event that occurs when the window has been closed

WINDOW_ACTIVATED The event that occurs when the window is activated — obtains the

focus, in other words When another GUI component has the focus,you could make the window obtain the focus by clicking on it, forexample

Table continued on following page

Handling Events

Trang 2

Event ID Description

WINDOW_DEACTIVATED The event that occurs when the window is deactivated — loses the

focus, in other words Clicking on another window would cause thisevent, for example

WINDOW_GAINED_FOCUS The event that occurs when the window gains the focus This implies

that the window or one of its components will receive keyboardevents

WINDOW_LOST_FOCUS The event that occurs when the window loses the focus This implies

that keyboard events will not be delivered to the window or any ofits components

WINDOW_ICONIFIED The event that occurs when the window is minimized and reduced to

an icon

WINDOW_DEICONIFIED The event that occurs when the window is restored from an icon.WINDOW_STATE_CHANGED The event that occurs when the window state changes — when it is

maximized or minimized, for instance

If any of these events occur, the processWindowEvent()method that you have added to the

SketchFrameclass will be called Your version of the method overrides the base class method that isinherited from java.awt.Window, and is responsible for passing the event to any listeners that havebeen registered The argument of type WindowEventthat is passed to the method will contain the event

ID that identifies the particular event that occurred To obtain the ID of the event, you call the getID()method for the event object, and compare that with the ID identifying the WINDOW_CLOSINGevent If theevent is WINDOW_CLOSING, you call the dispose()method for the window to close the window andrelease the system resources it is using You then close the application by calling the exit()methoddefined in the Systemclass

The getID()method is defined in the AWTEventclass, which is a superclass of all the low-level event classes I have discussed, so all event objects that encapsulate low-level events have this method.

In the SketchFrameclass, the dispose()method is inherited originally from the Windowclass via thebase class JFrame This method releases all the resources for the window object, including those for allcomponents owned by the object Calling the dispose()method doesn’t affect the window object itself

in the program It just tells the operating system that the resources used to display the window and thecomponents it contains on the screen are no longer required The window object is still around togetherwith its components, so you could call its methods or even open it again

Note that you call the processWindowEvent() method in the superclass if it is not

the closing event It is very important that you do this as it allows the event to be

passed on to any listeners that have been registered for these events If you don’t

call processWindowEvent() for the superclass, any events that you do not handle in

your processWindowEvent() method will be lost, because the base class method is

normally responsible for passing the event to the listeners that have been registered

to receive it.

Chapter 18

Trang 3

If you had not commented out the call to the setDefaultCloseOperation()method, yourprocessWindowEvent()method would still have been called when the close icon was clicked In thiscase you would not need to call dispose()and exit()in the method This would all have been takencare of automatically after your processWindowEvent()method had finished executing This would bepreferable as it means there would be less code in your program, and the code to handle the defaultclose action is there in the JFrameclass anyway.

Enabling Other Low-level EventsThe enableEvents()method is inherited from the Componentclass This means that any componentcan elect to handle its own events You just call the enableEvents()method for the component andpass an argument defining the events you want the component to handle If you want to enable morethan one type of event for a component, you just combine the event masks from AWTEventthat you sawearlier by linking them with a bitwise OR To make the window object handle mouse events as well aswindow events, you could write:

enableEvents(WINDOW_EVENT_MASK | MOUSE_EVENT_MASK);

Of course, you must now also implement the processMouseEvent()method for the SketchFrameclass Like the processWindowEvent()method, this method is protected and has voidas the returntype It receives the event as an argument of type MouseEvent There are two other methods specific tothe Windowclass that handle events:

processWindowFocusEvent(WindowEvent e) This method is called for any window focus

events that arise as long as such events areenabled for the window

processWindowStateEvent(WindowEvent e) This method is called for events arising as a

result of the window changing state

These methods and the processWindowEvent()method are available only for objects of type Window

or of a type that is a subclass of Window, so don’t try to enable window events on other components.The other event-handling methods that you can override to handle component events are:

processEvent(AWTEvent e) This method is called first for any events that are

enabled for the component If you implement thismethod and fail to call the base class method, none

of the methods for specific groups of events will becalled

processFocusEvent(FocusEvent e) This method will be called for focus events, if they

are enabled for the component

Table continued on following page

Handling Events

Trang 4

Event-Handling Methods Description

processKeyEvent(KeyEvent e) This method will be called for key events, if

they are enabled for the component

processMouseEvent(MouseEvent e) This method will be called for mouse

button events, if they are enabled for thecomponent

processMouseMotionEvent(MouseEvent e) This method will be called for mouse move

and drag events, if they are enabled for thecomponent

processMouseWheelEvent(MouseWheelEvent e) This method will be called for mouse wheel

rotation events, if they are enabled for thecomponent

All the event-handling methods for a component are protected methods that have a return type of void.The default behavior implemented by these methods is to dispatch the events to any listeners registeredfor the component If you don’t call the base class method when you override these methods after yourcode has executed, this behavior will be lost

Low-Level Event Listeners

To create a class that defines an event listener, your class must implement a listener interface All event tener interfaces extend the interface java.util.EventListener This interface doesn’t declare anymethods, though — it’s just used to identify an interface as being an event listener interface It also allows

lis-a vlis-arilis-able of type EventListenerto be used for storing a reference to any kind of event listener object.There is a very large number of event listener interfaces You’ll consider just eight at this point that areconcerned with low-level events The following sections describe these interfaces and the methods theydeclare

Although it seemed to be convenient to handle the window-closing event in the

SketchFrameclass by implementing processWindowEvent(), as a general rule you

should use listeners to handle events Using listeners is the recommended approach to

handling events in the majority of circumstances, since separating the event handling

from the object that originated the event results in a simpler code structure that is

eas-ier to understand and is less error prone You will change the handling of the

window-closing event in the Sketcher code to use a listener a little later in this chapter.

Chapter 18

Trang 5

The WindowListener InterfaceThis interface defines methods to respond to events reflecting changes in the state of a window.

windowOpened(WindowEvent e) Called the first time the window is openedwindowClosing(WindowEvent e) Called when the system menu Close item or the

window close icon is selectedwindowClosed(WindowEvent e) Called when the window has been closedwindowActivated(WindowEvent e) Called when the window is activated — by clicking

on it, for examplewindowDeactivated(WindowEvent e) Called when a window is deactivated — by click-

ing on another window, for examplewindowIconified(WindowEvent e) Called when a window is minimized and reduced

to an iconwindowDeiconified(WindowEvent e) Called when a window is restored from an icon

The WindowFocusListener InterfaceThis interface defines methods to respond to a window gaining or losing the focus When a window hasthe focus, one of its child components can receive input from the keyboard When it loses the focus, key-board input via a child component of the window is not possible

windowGainedFocus(WindowEvent e) Called when the window gains the focus such that

the window or one of its components will receivekeyboard events

windowLostFocus(WindowEvent e) Called when the window loses the focus After this

event, neither the window nor any of its nents will receive keyboard events

compo-The WindowStateListener InterfaceThis interface defines a method to respond to any change in the state of a window

windowStateChanged(WindowEvent e) Called when the window state changes — when it

is maximized or iconified, for example

Handling Events

Trang 6

The MouseListener Interface

This interface defines methods to respond to events arising when the mouse cursor is moved into or out

of the area occupied by a component, or one of the mouse buttons is pressed, released, or clicked

mouseClicked(MouseEvent e) Called when a mouse button is clicked on a component —

that is, when the button is pressed and releasedmousePressed(MouseEvent e) Called when a mouse button is pressed on a componentmouseReleased(MouseEvent e) Called when a mouse button is released on a componentmouseEntered(MouseEvent e) Called when the mouse enters the area occupied by a

componentmouseExited(MouseEvent e) Called when the mouse exits the area occupied by a

component

The MouseMotionListener Interface

This interface defines methods that are called when the mouse is moved or dragged with a buttonpressed

mouseMoved(MouseEvent e) Called when the mouse is moved within a componentmouseDragged(MouseEvent e) Called when the mouse is moved within a component while

a mouse button is held down

The MouseWheelListener Interface

This interface defines a method to respond to the mouse wheel being rotated This is frequently used toscroll information that is displayed, but you can use it in any way that you want

mouseWheelMoved(MouseWheelEvent e) Called when the mouse wheel is rotated

The KeyListener Interface

This interface defines methods to respond to events arising when a key on the keyboard is pressed orreleased

Chapter 18

Trang 7

Defined Methods Description

keyTyped(KeyEvent e) Called when a key on the keyboard is pressed and then releasedkeyPressed(KeyEvent e) Called when a key on the keyboard is pressed

keyReleased(KeyEvent e) Called when a key on the keyboard is released

The FocusListener InterfaceThis interface defines methods to respond to a component gaining or losing the focus You might imple-ment these methods to change the appearance of the component to reflect whether or not it has the focus

focusGained(FocusEvent e) Called when a component gains the keyboard focusfocusLost(FocusEvent e) Called when a component loses the keyboard focus

There is a further listener interface, MouseInputListener, that is defined in the javax.swing.eventpackage This listener implements both the MouseListenerand MouseMotionListenerinterfaces so itdeclares methods for all possible mouse events in a single interface

The WindowListener, WindowFocusListener, and WindowStateListenerinterfaces define methodscorresponding to each of the event IDs defined in the WindowEventclass that you saw earlier If youdeduced from this that the methods in the other listener interfaces correspond to event IDs for the otherevent classes, well, you’re right All the IDs for mouse events are defined in the MouseEventclass.These are:

The MOUSE_MOVEDevent corresponds to just moving the mouse The MOUSE_DRAGGEDevent arises whenyou move the mouse while keeping a button pressed

The event IDs that the KeyEventclass defines are:

Those defined in the FocusEventclass are:

Handling Events

Trang 8

public void windowIconified(WindowEvent e) {}

public void windowDeiconified(WindowEvent e) {}

public void windowActivated(WindowEvent e) {}

public void windowDeactivated(WindowEvent e) {}

private SketchFrame window; // The application windowprivate static Sketcher theApp; // The application object}

If you run the Sketcher program again, you will see it works just as before, but now the Sketcherclassobject is handling the close operation

How It WorksYou have added import statements for the WindowEventand WindowListenerclass names TheSketcherclass now implements the WindowListenerinterface, so an object of type Sketchercan handle window events The main()method now creates a Sketcherobject and stores the reference

in the static class member theApp.The createGUI()method is now an instance method, and this executes on the event-dispatchingthread as shown in the previous example The createGUI()method creates the application windowobject as before, but now the reference is stored in a field belonging to theApp After setting up the win-dow components, the createGUI()method calls the addWindowListener()method for the windowobject The argument to the addWindowListener()method is a reference to the listener object that is

to receive window events Here it is the variable this, which refers to the application object, theApp

If you had other listener objects that you wanted to register to receive this event, you would just need

to add more calls to the addWindowListener()method — one call for each listener

When you implement the WindowListenerinterface in the Sketcherclass, you must implement all sevenmethods that are declared in the interface If you failed to do this, the class would be abstract and youcould not create an object of type Sketcher Only the windowClosing()method contains code here —the bodies of all the other methods are empty because you don’t need to use them The windowClosing()method does the same as the processWindowEvent()method that you implemented for the previousversion of the SketchFrameclass, but here you don’t need to check the object passed to it because thewindowClosing()method is called only for a WINDOW_CLOSINGevent You don’t need to pass the event

on either; this is necessary only when you handle events in the manner I discussed earlier Here, if therewere other listeners around for the window events, they would automatically receive the event

You have included the code that calls dispose()and exit()here, but if you set the default close ation in SketchFrameto EXIT_ON_CLOSE, you could omit these, too You really need to put your appli-cation cleanup code only in the windowClosing()method, and this will typically display a dialog tojust prompt the user to save any application data You will get to that eventually

oper-Having to implement six methods that you don’t need is rather tedious But you have a way to get aroundthis — by using what is called an adapter class, to define a listener

Using Adapter Classes

An adapter class is a term for a class that implements a listener interface with methods that have no tent, so they do nothing The idea of this is to enable you to derive your own listener class from any of the

con-Handling Events

Trang 9

To implement a listener for a particular event type, you just need to implement the methods declared inthe corresponding interface You could handle some of the window events for the SketchFrameclass bymaking the application class the listener for window events.

Try It Out Implementing a Low-Level Event Listener

First, delete the call to the enableEvents()method in the SketchFrame()constructor Then delete thedefinition of the processWindowEvent()method from the class definition

Now you can modify the previous version of the Sketcherclass so that it is a listener for window events:// Sketching application

import java.awt.Dimension;

import java.awt.Toolkit;

import java.awt.event.WindowListener;

import java.awt.event.WindowEvent;

public class Sketcher implements WindowListener {

public static void main(String[] args) {

theApp = new Sketcher(); // Create the application objectSwingUtilities.invokeLater(

new Runnable() { // Anonymous Runnable class object

public void run() { // Run method executed in threadtheApp.creatGUI(); // Call static GUI creator}

} );

}

// Method to create the application GUI

private void creatGUI() {

window = new SketchFrame(“Sketcher”); // Create the app windowToolkit theKit = window.getToolkit(); // Get the window toolkitDimension wndSize = theKit.getScreenSize(); // Get screen size// Set the position to screen center & size to half screen sizewindow.setBounds(wndSize.width/4, wndSize.height/4, // Position

wndSize.width/2, wndSize.height/2); // Sizewindow.addWindowListener(this); // theApp as window listenerwindow.setVisible(true);

}

// Handler for window closing event

public void windowClosing(WindowEvent e) {

window.dispose(); // Release the window resourcesSystem.exit(0); // End the application

}

// Listener interface functions you must implement – but don’t need

public void windowOpened(WindowEvent e) {}

public void windowClosed(WindowEvent e) {}

Chapter 18

Trang 10

adapter classes that are provided, and then implement just the methods that you are interested in Theother empty methods will be inherited from the adapter class so you don’t have to worry about them.There’s an adapter class defined in the javax.swing.eventpackage that defines the methods for theMouseInputListenerinterface There are five further adapter classes defined in the java.awt.eventpackage that cover the methods in the other low-level listener interfaces you have seen:

The WindowAdapterclass implements all of the methods defined in the WindowListener,

WindowFocusListener, and WindowStateListenerinterfaces The other five each implementthe methods in the corresponding listener interface

To handle the window closing event for the Sketcherapplication, you could derive your own classfrom the WindowAdapterclass and just implement the windowClosing()method If you also make it

an inner class for the Sketcherclass, it will automatically have access to the members of the Sketcherobject, regardless of their access specifiers Let’s change the structure of the Sketcherclass once more

to make use of an adapter class

Try It Out Implementing an Adapter Class

The version of the Sketcherclass to implement this will be as follows, with changes to the previousversion highlighted:

public class Sketcher {

public static void main(String[] args) {

theApp = new Sketcher(); // Create the application objectSwingUtilities.invokeLater(

new Runnable() { // Anonymous Runnable class object

public void run() { // Run method executed in threadtheApp.creatGUI(); // Call static GUI creator}

} );

}

// Method to create the application GUI

private void creatGUI() {

window = new SketchFrame(“Sketcher”); // Create the app windowToolkit theKit = window.getToolkit(); // Get the window toolkitDimension wndSize = theKit.getScreenSize(); // Get screen size

Chapter 18

Trang 11

// Set the position to screen center & size to half screen sizewindow.setBounds(wndSize.width/4, wndSize.height/4, // Position

wndSize.width/2, wndSize.height/2); // Sizewindow.addWindowListener(new WindowHandler()); // Add window listenerwindow.setVisible(true); // Display the window}

// Handler class for window eventsclass WindowHandler extends WindowAdapter {// Handler for window closing eventpublic void windowClosing(WindowEvent e) {window.dispose(); // Release the window resourcesSystem.exit(0); // End the application

}}private SketchFrame window; // The application windowprivate static Sketcher theApp; // The application object}

This example will display the same application window as the previous example

How It Works

As the Sketcherclass is no longer the listener for window, it doesn’t need to implement theWindowListenerinterface The WindowHandlerclass is the listener class for windowevents Becausethe WindowHandlerclass is an inner class to the Sketcherclass, it has access to all the members of theclass, so calling the dispose()method for the windowobject is still quite straightforward — you justaccess the windowfield of the top-level class

The WindowAdapterobject that is the listener for the window object is created in the argument to theaddWindowListener() method for window You don’t need an explicit variable to contain it because itwill be stored in a data member of the Windowclass object This data member is inherited from theWindowsuperclass of the SketchFrameclass

You haven’t finished with level events yet by any means, and you’ll return to handling more level events in the next chapter when you begin to add drawing code to the Sketcher program In themeantime, let’s start looking at how you can manage semantic events

low-An easy mistake to make when you’re using adapter classes is to misspell the name

of the method that you are using to implement the event — typically by using the wrong case for a letter In this case, you won’t be overriding the adapter class method

at all; you’ll be adding a new method Your code will compile perfectly well but your program will not handle any events They will all be passed to the method in the adapter class with the name your method should have had — which does nothing, of course This can be a bit mystifying until you realize where the problem is.

Handling Events

Trang 12

Semantic Events

As you saw earlier, semantic events relate to operations on the components in the GUI for your program

If you select a menu item or click a button, for example, a semantic event is generated Three classes resent the basic semantic events you’ll be dealing with most of the time, and they are derived from theAWTEventclass, as shown in Figure 18-4

rep-Figure 18-4

An ActionEventis generated when you perform an action on a component such as clicking on amenu item or a button An ItemEventoccurs when a component is selected or deselected, and anAdjustmentEventis produced when an adjustable object, such as a scrollbar, is adjusted

Different kinds of components can produce different kinds of semantic events The components that canoriginate these events are:

Event Type Produced by Objects of Type

ItemEvent

These three sematic event classes are in java.awt.event

AdjustmentEventActionEvent

Chapter 18

Trang 13

Event Type Produced by Objects of Type

These three types of event are also generated by the old AWT components, but I won’t go into these here

as you are concentrating on the Swing components Of course, any class you derive from these nent classes to define your own customized components can be the source of the event that the baseclass generates If you define your own class for buttons —MyFancyButton, say — your class will haveJButtonas a base class and inherit all of the methods from the JButtonclass, and objects of your classwill originate events of type ActionEventand ItemEvent

compo-Quite a large number of semantic events are specific to Swing components Classes that haveAbstractButtonas a base, which includes menu items and buttons, can generate events of typeChangeEventthat signal some change in the state of a component Components corresponding to theJMenuItemclass and classes derived from JMenuItemcan generate events of type MenuDragMouseEventand of type MenuKeyEvent An AncestorEventis an event that is communicated to a child componentfrom a parent component You’ll look at the details of some of these additional events when you need tohandle them for the components in question

As with low-level events, the most convenient way to handle semantic events is to use listeners, so I’lldelve into the listener interfaces for semantic events next

Semantic Event Listeners

You have a listener interface defined for each of the three semantic event types that I have introduced sofar, and they each declare a single method:

Listener Interface Method

ActionListener void actionPerformed(ActionEvent e)

AdjustmentListener void adjustmentValueChanged(AdjustmentEvent e)

Since each of these semantic event listener interfaces declares only one method, there’s no need for sponding adapter classes The adapter classes for the low-level events were there only because of thenumber of methods involved in each listener interface To define your semantic event listener objects, youjust define a class that implements the appropriate listener interface You can try that out by implementing

corre-a simple corre-applet now, corre-and then see how you ccorre-an decorre-al with semcorre-antic events in corre-a more compliccorre-ated context

by adding to the Sketcher program later

Handling Events

Trang 14

import java.util.Random; // For random number generatorimport java.awt.event.ActionListener;

// Set up the lucky numbers buttons

// Set up the control buttons

}// Class defining custom buttons showing lottery selection// Each button listens for its own events

class Selection extends JButton implements ActionListener {// Constructor

public Selection(int value) {// Create the button showing the value

}// Handle selection button eventpublic void actionPerformed(ActionEvent e) {// Change the current selection value to a new selection value}

// Details of the rest of the selection class definition

}// Class defining a handler for a control buttonclass HandleControlButton implements ActionListener {// Constructor

// Handle button clickpublic void actionPerformed(ActionEvent e) {// Handle button click for a particular button

}// Rest of the inner class definition

}final static int numberCount = 6; // Number of lucky numbersfinal static int minValue = 1; // Minimum in rangefinal static int maxValue = 49; // Maximum in rangestatic int[] values = new int[maxValue-minValue+1]; // Array of possible valuesstatic { // Initialize array

for(int i = 0 ; i<values.length ; i++)values[i] = i + minValue;

}

Handling Events

Trang 15

Semantic Event Handling in Applets

Event handling in an applet is exactly the same as in an application, but you ought to see it for yourself.Let’s see how you might handle events for buttons in an applet You can create an applet that uses somebuttons that have listeners To make this example a bit more gripping, I’ll throw in the possibility ofmonetary gain That’s interesting to almost everybody

Let’s suppose you want to implement an applet that will create a set of random numbers for a lotteryentry The requirement is to generate six different random numbers between 1 and 49 It would also benice to be able to change a single number if you don’t like it, so you’ll add that capability as well Sincethe local lottery may not be like this, you’ll implement the applet to make it easily adaptable to fit localrequirements

By displaying the six selected numbers on buttons, you can provide for changing one of the choices byprocessing the action event for that button Thus, clicking a button will provide another number You’llalso add a couple of control buttons, one to make a new selection for a complete set of lottery numbers,and another just for fun to change the button color Figure 18-5 shows how the applet will look whenrunning under appletviewer:

Figure 18-5

Try It Out A Lottery Applet

You can outline the broad structure of the applet’s code as follows:

// Applet to generate lottery entries

Trang 16

// An array of custom buttons for the selected numbers

private Selection[] luckyNumbers = new Selection[numberCount];

private static Random choice = new Random(); // Random number generator}

How It Works

The applet class is called Lottery, and it contains two inner classes, Selectionand

HandleControlButton The Selectionclass provides a custom button that will show a number

as its label, the number being passed to the constructor as an argument You can make an object oftheSelectionclass listen for its own action events As I said at the outset, an event for a selection button will change the label of the button to a different value, so of course, you’ll need to make surethis doesn’t duplicate any of the values for the other buttons

The two control buttons will use separate listeners to handle their action events, and the response to anevent will be quite different for each of them One control button will create a new set of lucky numberswhile the other control button will just change the color of the buttons

The numberCountmember of the Lotteryclass sets the number of values that is created The minValueand maxValuemembers specify the range of possible values that lottery numbers can have The possiblevalues for selections are stored in the valuesarray, and this is set up in the static initialization block TheLotteryclass has an array of Selectionobjects as a data member — you can have arrays of componentsjust like arrays of any other kind of object Since the Selectionbuttons will all be the same, it’s very con-venient to create them as an array, and having an array of components enables you to set them up in aloop You also have a Randomobject as a class member that you’ll use to generate random integers.You can now set about filling in the sections of the program that you have roughed out

Filling in the Details

The generation of maxCountrandom values from the elements in the valuesarray is quite independent

of everything else here, so you can define a static method in the Lotteryclass to do this:

public class Lottery extends JApplet {

// Generate numberCount random selections from the values array

static int[] getNumbers() {

int[] numbers = new int[numberCount]; // Store for the numbers to be returnedint candidate = 0; // Stores a candidate selection

for(int i = 0; i < numberCount; i++) { // Loop to find the selectionssearch:

// Loop to find a new selection different from any found so farfor(;;) {

Trang 17

numbers[i] = candidate; // Store the selection in numbers arraybreak; // and go to find the next

}}return numbers; // Return the selections}

// Plus the rest of the class definition

}The getNumbers()method returns a reference to an array of values of type intthat represent the selections — which must all be different, of course You start the process by creating an array to hold theselections, and a variable, candidate, to hold a potential selection for the valuesarray You generate anew selection for each iteration of the outer forloop The process for finding an acceptable selection isquite simple In the indefinite forloop with the label search, you choose a random value from the valuesarray using the random number generator and then check its value against any selections alreadystored in the numbersarray If it is the same as any of them, the labeled continuestatement will go to thenext iteration of the indefinite forloop This will continue until a selection is found that is different fromthe others In this way you ensure that you end up with a set of selections that are all different

Let’s implement the init()method and the creatGUI()method for the Lotteryclass next, as theseset up the Selectionbuttons and the rest of the applet

Try It Out Setting Up the Lucky Number Buttons

The init()method has to execute only the createGUI()method on the event-dispatching thread:// Initialize the applet

public void init() {SwingUtilities.invokeLater( // Create interface componentsnew Runnable() { // on the event dispatching threadpublic void run() {

createGUI();

}} );

}

In the class outline, you identified two tasks for the createGUI()method The first was setting up thelucky number buttons to be contained in the luckyNumbersarray

Here’s the code to do that:

// Create User Interface for appletpublic void creatGUI() {

// Set up the selection buttonsContainer content = getContentPane();

content.setLayout(new GridLayout(0,1)); // Set the layout for the applet// Set up the panel to hold the lucky number buttons

JPanel buttonPane = new JPanel(); // Add the pane containing numbers

Handling Events

Trang 18

Try It Out Setting Up the Control Buttons

The listeners for each of the control buttons will be of the same class type, so the listener object will needsome way to determine which button originated a particular event One way to do this is to use con-stants as IDs to identify the control buttons and pass the appropriate ID to the class constructor for thelistener object

You can define the constants PICK_LUCKY_NUMBERSand COLORas fields in the Lotteryclass for thispurpose The COLORcontrol button will also reference a couple of variables of type Color, startColor,and flipColor You can add the following statements to the Lotteryclass after the definition of theluckyNumbersarray:

// An array of custom buttons for the selected numbersprivate Selection[] luckyNumbers = new Selection[numberCount];

final public static int PICK_LUCKY_NUMBERS = 1; // Select button IDfinal public static int COLOR = 2; // Color button ID// swap colors

Color flipColor = new Color(Color.YELLOW.getRGB()^Color.RED.getRGB());

Color startColor = Color.YELLOW; // start colorThe startColorfield is initialized with the YELLOWcolor from the Colorclass The flipColorfield isset to the color that results from ORing the colors YELLOWand RED Of course, to get a sensible color as aresult you must OR the RGB values that you obtain from the Colorobjects, not the references to theColorobjects! You’ll be using the flipColorfield to change the color of the buttons

The code to add the other panel and the control buttons is as follows:

// Create User Interface for appletpublic void createGUI() {

// Setting up the selections buttons as previously

// Add the pane containing control buttonsJPanel controlPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 10)); // Add the two control buttons

JButton button; // A button variableDimension buttonSize = new Dimension(100,20); // Button sizecontrolPane.add(button = new JButton(“Lucky Numbers!”));

Trang 19

// Let’s have a fancy panel borderbuttonPane.setBorder(BorderFactory.createTitledBorder(

// Set up the control buttons

}

How It Works

The init()method uses the invokeLater()method from the SwingUtilitiesclass to execute thecreateGUI()method on the event-dispatching thread This guarantees that there is no possibility of adeadlock arising in the GUI construction process This is the same technique that you used in the previ-ous example

The first step in the createGUI()method is to define the layout manager for the applet To make thelayout easier, you’ll use one panel to hold the selection buttons and another to hold the control buttons.You position these panels one above the other by specifying the layout manager for the content pane ofthe applet as a grid layout with one column The top panel will contain the lucky number buttons andthe bottom panel will contain the control buttons

The buttonPanepanel that holds the lucky number buttons is of type JPanel, so it has a FlowLayoutobject as its layout manager by default A flow layout manager allows components to assume their

“natural” or “preferred size,” so you’ll set the preferred size for the buttons in the Selectionclass structor You decorate the panel with a border by calling its setBorder()method The argument is thereference that is returned by the static createTitledBorder()method from the BorderFactoryclass.The first argument passed to createTitledBorder()is the border to be used, and the second argu-ment is the title

con-You use an etched border that is returned by another static method in the BorderFactoryclass Thetwo arguments to this method are the highlight and shadow colors to be used for the border A bigadvantage of using the BorderFactorymethods rather than creating border objects from the borderclass constructors directly is that border objects will be shared where possible, so you can use a particu-lar border in various places in your code and only one object will be created

The buttons to display the chosen numbers will be of type Selection, and you’ll get to the detail of thisinner class in a moment You call the static getNumbers()method to obtain the first set of random val-ues for the buttons You then create and store each button in the luckyNumbersarray and add it to thepanel in the forloop Since these buttons are going to listen for their own events, you don’t need toworry about setting separate action listeners for them The last step here is to add the buttonPanepanel

to the content pane for the applet

You can now add the code for the control buttons to the createGUI()method

Chapter 18

Trang 20

How It Works

You create another JPanelobject to hold the control buttons and just to show that you can, you pass alayout manager object to the constructor It’s a FlowLayoutmanager again, but this time you explicitlyspecify that the components are to be centered and the horizontal and vertical gaps are to be 5 and 10pixels, respectively

You declare the buttonvariable to use as a temporary store for the reference to each button while youset it up You also define a Dimensionobject that you’ll use to set a common preferred size for the but-tons The buttons in this case are JButtoncomponents, not custom components, so you must set each ofthem up here with a listener and a border You add a raised bevel border to each button to make themlook like buttons — again using a BorderFactorymethod

The listener for each button is an object of the inner class HandleControlButton, and you pass theappropriate button ID to the constructor for reasons that will be apparent when you define that class

To set the preferred size for each button object, you call its setPreferredSize()method The ment is a Dimensionobject that specifies the width and height Finally, after adding the two buttons tocontrolPane, you add that to the content pane for the applet

argu-The inner class HandleControlButtondefines the listener object for each control button, so let’s ment that next

imple-Try It Out Defining the Control Button Handler Class

You have already determined that the HandleControlButtonclass constructor will accept an argumentthat identifies the particular button for which it is listening This is to enable the actionPerformed()method in the listener class to choose the course of action appropriate to the button Here’s the innerclass definition to do that:

class HandleControlButton implements ActionListener {

// Constructorpublic HandleControlButton(int buttonID) {this.buttonID = buttonID; // Store the button ID}

// Handle button clickpublic void actionPerformed(ActionEvent e) {switch(buttonID) {

}}

Chapter 18

Trang 21

private int buttonID;

}How It WorksThe constructor stores its argument value in the buttonIDfield so each listener object will have the IDfor the button available The actionPerformed()method uses the button ID to select the appropriatecode to execute for a particular button Each case in the switchstatement corresponds to a different but-ton You could extend this to enable the class to handle as many different buttons as you want by addingcase statements Because of the way you have implemented the method, each button must have a unique

ID associated with it Of course, this isn’t the only way to do this, as you’ll see in a moment

For the PICK_LUCKY_NUMBERSbutton event, you just call the getNumbers()method to produce a set ofnumbers, and then call the setValue()method for each selection button and pass a number to it You’llimplement the setValue()method when you define the Selectionclass in detail, in a moment.For the COLORbutton event, you create a new color by exclusive ORing (that is, XOR) the RGB value offlipColorwith the current button color You’ll recall from the discussion of the ^operator (in Chapter 2)that you can use it to exchange two values, and that is what you are doing here You defined flipColor

as the result of exclusive ORing the two colors, Color.YELLOWand Color.RED, together ExclusiveORing flipColorwith either color will produce the other color, so you flip from one color to the otherautomatically for each button by exclusive ORing the background and flipColor As I said earlier, youmust get the RGB value for each color and operate on those — you can’t apply the ^operator to the objectreferences You then turn the resulting RGB value back into a Colorobject

Let’s now add the inner class, Selection, which defines the lucky number buttons

Try It Out Defining the Selection Buttons

Each button will need to store the value shown on the label, so the class will need a data member for thispurpose The class will also need a constructor, a setValue()method to set the value for the button to anew value, and a method to compare the current value for a button to a given value You need to be able

to set the value for a button for two reasons — you’ll need the capability when you set up all six tions in the listener for the control button, and you’ll want to reset the value for a button to change itindividually

selec-The method to compare the value set for a button to a given integer will enable you to exclude a numberthat was already assigned to a button when you are generating a new set of button values You’ll alsoneed to implement the actionPerformed()method to handle the action events for the button, as thebuttons are going to handle their own events Here’s the basic code for the class definition:

class Selection extends JButton implements ActionListener {// Constructor

public Selection(int value) {super(Integer.toString(value)); // Call base constructor and set the labelthis.value = value; // Save the value

Trang 22

// Handle selection button event

public void actionPerformed(ActionEvent e) {

// Change this selection to a new selectionint candidate = 0;

for(;;) { // Loop to find a different selectioncandidate = values[choice.nextInt(values.length)];

if(isCurrentSelection(candidate)) { // If it is not differentcontinue; // find another

}setValue(candidate); // We have one so set the button valuereturn;

}}

// Set the value for the selection

public void setValue(int value) {

setText(Integer.toString(value)); // Set value as the button labelthis.value = value; // Save the value

}

// Check the value for the selection

boolean hasValue(int possible) {

return value==possible; // Return true if equals current value }

// Check the current choices

boolean isCurrentSelection(int possible) {

for(int i = 0; i < numberCount; i++) { // For each buttonif(luckyNumbers[i].hasValue(possible)) { // check against possiblereturn true; // Return true for any =}

}return false; // Otherwise return false }

private int value; // Value for the selection button

}

How It Works

The constructor calls the base class constructor to set the initial label for the button It also stores thevalue of type intthat is passed as an argument The setValue()method just updates the value for aselection button with the value passed as an argument and changes the button label by calling thesetText()method, which is inherited from the base class, JButton The hasValue()method returnstrueif the argument value passed to it is equal to the current value stored in the data member value,and falseotherwise

The actionPerformed()method has a little more meat to it, but the technique is similar to that in thegetNumbers()method To change the selection, you must create a new random value for the button fromthe numbers valuesarray, but excluding all the numbers currently assigned to the six buttons To do thisyou just check each candidate against the six existing selections by calling the isCurrentSelection()method and continue choosing a new candidate until you find one that’s different

Chapter 18

Trang 23

In the isCurrentSelection()method, you just work through the array of Selectionobjects,luckyNumbers, comparing each value with the possibleargument using the hasValue()method.

If any button has the same value as possible, the method returns true; otherwise, it returns false.You’re ready to start generating lottery entries If you compile the Lottery.javafile, you can run theapplet using appletviewer You will need an HTML file, of course The following contents for the filewill do the job:

<APPLET CODE=”Lottery.class” WIDTH=300 HEIGHT=200>

</APPLET>

You can adjust the width and height values to suit your monitor resolution if necessary

The applet should produce a selection each time you click the left control button Clicking any of theselection buttons will generate an action event that causes a new value to be created for the button Thisenables you to replace any selection that you know to be unlucky with an alternative

Undoubtedly, anyone who profits from using this applet will have immense feelings of gratitude and indebtedness towards the author, who will not be offended in the slightest by any offers of a portion of that success, however large!

Alternative Event-Handling Approaches

As I indicated in the discussion, there are various approaches to implementing listeners Let’s look at acouple of other ways in which you could have dealt with the control button events

Instead of passing a constant to the listener class constructor to identify which button was selected, youcould have exploited the fact that the event object has a method, getSource(), that returns a reference

to the object that is the source of the event To make use of this, a reference to both button objects wouldneed to be available to the actionPerformed()method You could easily arrange for this to be the case

by adding a couple of fields to the Lotteryclass:

JButton pickButton = new JButton(“Lucky Numbers!”);

JButton colorButton = new JButton(“Color”);

The inner class could then be defined as follows:

class HandleControlButton implements ActionListener {// Handle button click

public void actionPerformed(ActionEvent e) {Object source = e.getSource(); // Get source object referenceif(source == pickButton) { // Is it the pick button?

int[] numbers = getNumbers(); // Get maxCount random numbersfor(int i = 0; i < numberCount; i++) {

luckyNumbers[i].setValue(numbers[i]); // Set the button values}

} else if(source == colorButton) { // Is it the color button?

Color color = new Color(

flipColor.getRGB()^luckyNumbers[0].getBackground().getRGB());

Handling Events

Trang 24

for(int i = 0; i < numberCount; i++) {luckyNumbers[i].setBackground(color); // Set the button colors}

}}}

You no longer need to define a constructor, as the default will do The actionPerformed()methodnow decides what to do by comparing the reference returned by the getSource()method for the eventobject with the two button references in the JButtonfields of the Lotteryclass With the previous ver-sion of the listener class, you stored the ID as a data member, so a separate listener object was needed foreach button In this case there are no data members in the listener class, so you can use one listenerobject for both buttons

The code to add these buttons in the createGUI()method would then be:

// Add the two control buttonsDimension buttonSize = new Dimension(100,20);

The only fundamental difference here is that you use one listener object for both buttons

There is another possible way to implement listeners for these buttons You could define a separate classfor each listener — this would not be unreasonable, as the actions to be performed in response to thesemantic events for each button are quite different You could use anonymous classes in this case — as Idiscussed back in Chapter 6 You could do this by adding the listeners for the button objects in thecreateGUI()method like this:

// Add the two control buttonsDimension buttonSize = new Dimension(100,20);

Chapter 18

Trang 25

for(int i = 0; i < numberCount; i++) {luckyNumbers[i].setValue(numbers[i]);

}}});

colorButton.addActionListener(

new ActionListener() {public void actionPerformed(ActionEvent e) {Color color = new Color(flipColor.getRGB()^luckyNumbers[0]

.getBackground().getRGB());

for(int i = 0; i < numberCount; i++) {luckyNumbers[i].setBackground(color);

}}});

Handling Low-Level and Semantic Events

I said earlier in this chapter that a component generates both low-level and semantic events, and youcould handle both if you want I can demonstrate this quite easily with a small extension to the Lotteryapplet Suppose you want to change the cursor to a hand cursor when it is over one of the selection but-tons This would be a good cue that you can select these buttons individually You can do this by adding

a mouse listener for each button

Try It Out A Mouse Listener for the Selection Buttons

There are many ways in which you could define the listener class Here you’ll define it as a separateclass called MouseHandler:

// Mouse event handler for a selection buttonimport java.awt.Cursor;

import java.awt.event.MouseEvent;

import java.awt.event.MouseAdapter;

class MouseHandler extends MouseAdapter {Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);

Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);

// Handle mouse entering the selection buttonpublic void mouseEntered(MouseEvent e) {e.getComponent().setCursor(handCursor); // Switch to hand cursor}

Handling Events

Trang 26

// Handle mouse exiting the selection button

public void mouseExited(MouseEvent e) {

e.getComponent().setCursor(defaultCursor); // Change to default cursor}

}

All you need to do to expedite this is to add a mouse listener for each of the six selection buttons Youneed only one listener object and after creating this you need to change the loop only in the createGUI()method for the applet to add the listener:

int[] choices = getNumbers(); // Get initial set of numbers MouseHandler mouseHandler = new MouseHandler(); // Create the listener

for(int i = 0 ; i<numberCount ; i++) {luckyNumbers[i] = new Selection(choices[i]);

luckyNumbers[i].addMouseListener(mouseHandler);

buttonPane.add(luckyNumbers[i]);

}How It Works

The mouseEntered()method will be called when the mouse enters the area of the component withwhich the listener is registered, and the method then changes the cursor for the component to a handcursor When the cursor is moved out of the area occupied by the component, the mouseExited()method is called, which restores the default cursor

Just two extra statements in createGUI()create the listener object and then add it for each selectionbutton within the loop If you recompile the applet and run it again, a hand cursor should appear when-ever the mouse is over the selection buttons Of course, you are not limited to just changing the cursor inthe event handler You could highlight the button by changing its color for instance You could apply thesame technique for any kind of component where the mouse is the source of actions for it

Semantic Event Listeners in an Application

The Sketcher program is an obvious candidate for implementing semantic event listeners to support theoperation of the menu bar in the SketchFrameclass When you click on an item in one of the pull-downmenus, a semantic event will be generated that you can listen for and then use to determine the appro-priate program action

Listening to Menu Items

Let’s start with the Elementsmenu This is concerned with identifying the type of graphic element to

be drawn next, and the color in which it will be drawn You won’t be drawing them for a while, but youcan put in the infrastructure to set the type and color for an element without worrying about how it willactually be created and drawn

To identify the type of element, you can define constants that will act as IDs for the four types of elementyou have provided for in the menu so far This will help with the operation of the listeners for the menuitem as well as provide a way to identify a particular type of element Since you’ll accumulate quite anumber of application-wide constants, it will be convenient to define them as static fields in a class from

Chapter 18

Trang 27

which they can be imported statically To be able to import the static fields, the class must be in a namedpackage, so let’s set that up To put the class in a package with the name Constants, you need to set up

a directory with this name at a suitable location on your disk, and then use the -classpathoptionwhen you compile the class in the Constantspackage to identify the path to the Constantsdirectory.Here’s the initial definition, including constants to define line, rectangle, circle, and curve elements:// Defines application wide constants

package Constants;

public class SketcherConstants { // Element type definitionspublic final static int LINE = 101;

public final static int RECTANGLE = 102;

public final static int CIRCLE = 103;

public final static int CURVE = 104;

// Initial conditionspublic final static int DEFAULT_ELEMENT_TYPE = LINE;

}Save this as SketcherConstants.javain the Constantsdirectory you have created Each elementtype ID in the class is an integer constant with a unique value, and you can obviously extend the variety

of element types if necessary Of course, you could also have defined the element IDs as enumerationconstants, but then you would have more than one class involved in the definition of constants forSketcher

You have defined the DEFAULT_ELEMENT_TYPEconstant to specify the initial element type to applywhen the Sketcher application starts You could do the same thing for the Colorsubmenu and supply

a constant that specifies the default initial element color:

// Defines application wide constantspackage Constants;

import java.awt.Color;

public class SketcherConstants { // Element type definitionspublic final static int LINE = 101;

public final static int RECTANGLE = 102;

public final static int CIRCLE = 103;

public final static int CURVE = 104;

// Initial conditionspublic final static int DEFAULT_ELEMENT_TYPE = LINE;

public final static Color DEFAULT_ELEMENT_COLOR = Color.BLUE;

}You have defined the DEFAULT_ELEMENT_COLORfield as type Color, so you have added an importstatement for the Colorclass name When you want to change the default startup color or element type,you just need to change the values of the constants in the SketcherConstantsclass This will automati-cally take care of setting things up — as long as you implement the program code appropriately

You can add fields to the SketchFrameclass to store the current element type and color, since these areapplication-wide values, and are not specific to a view:

Handling Events

Trang 28

colorMenu.add(blueItem = new JCheckBoxMenuItem(

“Blue”, elementColor.equals(BLUE)));

// Add element color accelerators

// plus the rest of the constructor as before

}// plus the rest of the class and include the two new data members

private Color elementColor = DEFAULT_ELEMENT_COLOR; // Current element colorprivate int elementType = DEFAULT_ELEMENT_TYPE; // Current element type}

You have imported static constants from the Colorclass, so you can use the names of the standard colorobjects that the class defines without qualifying them When you construct the element objects, you usethe elementTypeand elementColormembers to set the state of each menu item Only the elementtype menu item corresponding to the default type set in elementTypewill be checked because that’s theonly comparison that will produce a true result as an argument to the JRadioButtonMenuItemcon-structor The mechanism is the same for the color menu items, but note that you use the equals()method defined in the Colorclass for a valid comparison of Colorobjects You might just get awaywith using ==because you are using only constant Colorvalues that are defined in the class, but assoon as you use a color that is not one of these, this would no longer work Of course, you have to use ==for the element type items because the IDs are of type int

At this point it would be a good idea to recompile Sketcher to make sure everything is as it should be.Because you now have your own package containing the SketcherConstantsclass definition, you mustuse the -classpathoption to tell the compiler where to find it Assuming the Constantsdirectory is a sub-directory of the C:/Packagesdirectory, and the current directory is the one containing Sketcher.javaand SketchFrame.java, you will need to use the following command to compile Sketcher:

javac -classpath “.;C:/Packages” Sketcher.javaThe -classpathoption defines two paths: the current directory, specified by the period, and C:/Packages,which is the path to the Constantsdirectory that contains the SketcherConstants.javasource file Thiscommand should compile everything, including your package

Having got that sorted out, you can have a go at implementing the listeners for the Elementsmenu,starting with the type menu items

Try It Out Handling Events for the Element Type Menu

You’ll add an inner class to SketchFramethat will define listeners for the menu items specifying the ment type This class will implement the ActionListenerinterface because you want to respond toactions on these menu items Add the following definition as an inner class to SketchFrame:// Handles element type menu items

ele-class TypeListener implements ActionListener { // Constructor

TypeListener(int type) {this.type = type;

}

Handling Events

Trang 29

private Color elementColor = DEFAULT_ELEMENT_COLOR; // Current element colorprivate int elementType = DEFAULT_ELEMENT_TYPE; // Current element typeYou can now use these to ensure that the menu items are checked appropriately when the applicationstarts Of course, for the class to compile, you also want the names of the constants from the

SketcherConstantsclass imported into the SketchFrameclass, so make the following changes to theSketchFrameclass definition:

import static java.awt.event.InputEvent.*;

import static java.awt.AWTEvent.*;

import java.awt.event.WindowEvent;

import java.awt.Color;

import static java.awt.Color.*;

import static Constants.SketcherConstants.*;

public class SketchFrame extends JFrame {

// Constructor

public SketchFrame(String title) {

setTitle(title); // Set the window titlesetJMenuBar(menuBar); // Add the menu bar to the windowsetDefaultCloseOperation(EXIT_ON_CLOSE);

// Code to create the File menu

// Construct the Element drop-down menu

elementMenu.add(lineItem = new JRadioButtonMenuItem(

“Line”, elementType==LINE));

elementMenu.add(rectangleItem = new JRadioButtonMenuItem(

“Rectangle”, elementType==RECTANGLE));elementMenu.add(circleItem = new JRadioButtonMenuItem(

“Circle”, elementType==CIRCLE));

elementMenu.add(curveItem = new JRadioButtonMenuItem(

“Curve”, elementType==CURVE));

ButtonGroup types = new ButtonGroup();

// plus the rest of the code for the element types as before

Trang 30

// Sets the element type

public void actionPerformed(ActionEvent e) {

// Add type menu item listeners

lineItem.addActionListener(new TypeListener(LINE));

rectangleItem.addActionListener(new TypeListener(RECTANGLE));

circleItem.addActionListener(new TypeListener(CIRCLE));

curveItem.addActionListener(new TypeListener(CURVE));

menuBar.add(fileMenu); // Add the file menu

menuBar.add(elementMenu); // Add the element menu

How It Works

The application window won’t look any different, as the listeners just set the current element type in theSketchFrameobject The listener class is remarkably simple Each listener object stores the type corre-sponding to the menu item that is passed as the constructor argument When an event occurs, theactionPerformed()method just stores the type in the listener object in the elementTypemember ofthe SketchFrameobject

Now you can do the same for the color menu items

Try It Out Implementing Color Menu Item Listeners

You’ll define another inner class to SketchFramethat defines listeners for the Colormenu items:// Handles color menu items

class ColorListener implements ActionListener {

public ColorListener(Color color) {this.color = color;

}

Chapter 18

Trang 31

public void actionPerformed(ActionEvent e) {elementColor = color;

}private Color color;

}You just need to create listener objects and add them to the color menu items Add the following code atthe end of the SketchFrameconstructor after the code that sets up the Colorsubmenu:

// Add color menu item listenersredItem.addActionListener(new ColorListener(RED));

This adds a listener object for each menu item in the Colormenu

How It WorksThe ColorListenerclass works in the same way as the TypeListenerclass Each class object stores anidentifier for the menu item for which it is listening — in this case a Colorobject corresponding to thecolor the menu item sets up The actionPerformed()method just stores the Colorobject from the lis-tener object in the elementColormember of the SketchFrameobject

Of course, the menu doesn’t quite work as it should The Colormenu item check marks are not being setcorrectly, as you can see in Figure 18-6 You want an exclusive check, as with the radio buttons; havingmore than one color checked at one time doesn’t make sense

Figure 18-6

Handling Events

Trang 32

Fixing the Color Menu Check Marks

One way to deal with the problem is to make the listener object for a color menu item set the checkmarks for all the menu items You could code this in the ColorListenerclass as follows:

class ColorListener implements ActionListener {

public void actionPerformed(ActionEvent e) {elementColor = color;

// Set the checks for all menu itemsredItem.setState(color.equals(RED));

greenItem.setState(color.equals(GREEN));

blueItem.setState(color.equals(BLUE));

yellowItem.setState(color.equals(YELLOW));

}// Rest of the class as before

}

This calls the setState()method for each menu item If the argument to the method is truethe checkmark is set, and if it is false, it isn’t Clearly this will set the check mark only for the item that corre-sponds to the color referenced by color This is quite straightforward, but there is a better way

AButtonGroupobject works with JCheckBoxMenuItemobjects because they have AbstractButtonas

a base class Therefore, you could add these menu items to their own button group in the SketchFrameconstructor, and it will all be taken care of for you The ButtonGroupobject tracks the state of all of thebuttons in the group When any button is turned on, all the others are turned off, so only one button inthe group can be on at one time So add the following code — it could go anywhere after the items havebeen created but place it following the code that adds the items to the Colormenu for consistency withthe element type code:

ButtonGroup colors = new ButtonGroup(); // Color menu items button groupcolors.add(redItem);

of doing the same thing all over again in the toolbar context Of course, the only reason I brought it up,

as I’m sure you’ve anticipated, is that there is another way of working with menus, and that is to use anActionobject

An Actionobject is a bit of a strange beast It can be quite hard to understand at first, so I’ll take itslowly First of all let’s look at what is meant by an “action” here, as it is a precise term in this context

An action is an object of any class that implements the javax.swing.Actioninterface This interface

Chapter 18

Trang 33

declares methods that operate on an action object — for example, storing properties relating to theaction, enabling it and disabling it The Actioninterface happens to extend the ActionListenerinter-face, so an action object is a listener as well as an action Now that you know an Actionobject can getand set properties and is also a listener, how does that help us in implementing the Sketcher GUI?The answer is in the last capability of an Actionobject Some Swing components, such as those of typeJMenuand JToolBar, have an add()method that accepts an argument of type Action When you add

an Actionobject to these using the add()method, the method creates a component from the Actionobject that is automatically of the right type If you add an Actionobject to a JMenuobject, a JMenuItemwill be created and returned by the add()method On the other hand, when you add exactly the sameActionobject to a JToolBarobject, an object of type JButtonwill be created and returned This meansthat you can add the very same Actionobject to both a menu and a toolbar, and since the Actionobject

is its own listener, you automatically get both the menu item and the toolbar button supported by thesame action Clever, eh?

First, you should look at the Actioninterface

The Action Interface

In general, properties are items of information that relate to a particular object and are stored as part of

the object Properties are often stored in a map, where a key identifies a particular property, and the valuecorresponding to that property can be stored in association with the key The Propertiesclass that isdefined in the java.utilpackage does exactly that The Actioninterface has provision for storing sevenbasic standard properties that relate to an Actionobject:

A name— AStringobject that is used as the label for a menu item or a toolbar button

A small icon— Ajavax.swing.Iconobject to be displayed on a toolbar button

A short description of the action— AStringobject to be used as a tooltip

An accelerator key for the action— Defined by a javax.swing.KeyStrokeobject

A long description of the action— AStringobject that is intended to be used as sensitive help

context-❑ A mnemonic key for the action— This is a key code of type int

An action command key— Defined by an entry in a javax.swing.ActionMapobject associatedwith a component The ActionMapobject for a component defines mappings between objectsthat are keys and actions

Just so you are aware of them I have included the complete set here, but you will concentrate on just usingthe first three You haven’t met Iconobjects before, but you’ll get to them a little later in this chapter.You are not obliged to provide for all of these properties in your action classes, but the Actioninterfaceprovides the framework for it These properties are stored internally in a map collection in your actionclass, so the Actioninterface defines constants that you use as keys for each of the standard properties.These constants are all of type String, and the ones you are interested in are NAME, SMALL_ICON, andSHORT_DESCRIPTION The others are ACCELERATOR_KEY, LONG_DESCRIPTION, MNEMONIC_KEY, andACTION_COMMAND_KEY There is another constant of type Stringdefined in the interface with the nameDEFAULT, but this is not used currently The Actioninterface also declares the following methods:

Handling Events

Trang 34

Method Description

void putValue(String key, Object value) Stores the value with the key keyin the

map supported by the action class Tostore the name of an action within aclass method, you might write:

putValue(NAME, theName);

This uses the standard key NAMEto storethe object theName

Object getValue(String key) This retrieves the object from the map

corresponding to the key key Toretrieve a small icon within an actionclass method, you might write:

Icon lineIcon = (Icon)getValue(SMALL_ICON);

enabled and falseotherwise

void setEnabled(boolean state) This sets the Actionobject as enabled if

the argument state is trueand disabled

if it is false This operates on both thetoolbar button and the menu item if theyhave been created using the same object

void addPropertyChangeListener( This adds the listener passed as an

argu-PropertyChangeListener listener) ment, which listens for changes to

prop-erties such as the enabled state of theobject This is used by a container for anActionobject to track property changes

void removePropertyChangeListener( This removes the listener passed as an

PropertyChangeListener listener) argument This is also for use by a

Containerobject

Of course, since the Actioninterface extends the ActionListenerinterface, it also incorporates theActionPerformed()method that you are already familiar with So far, all you seem to have gainedwith this interface is a license to do a lot of work in implementing it, but it’s not as bad as that Thejavax.swingpackage defines the AbstractActionclass that already implements the Actioninter-face If you extend this class to create your own action class, you get a basic infrastructure for free Let’stry it out in the context of Sketcher

Chapter 18

Trang 35

Using Actions as Menu Items

This will involve major surgery on the SketchFrameclass Although you’ll be throwing away all thosefancy varieties of menu items you spent so much time putting together, at least you know how they worknow, and you’ll end up with much less code after re-engineering the class, as you’ll see As the saying goes,you’ve got to crack a few eggs to make a soufflé

You’ll go back nearly to square one and reconstruct the class definition First you’ll delete a lot of codefrom the existing class definition Comments will show where you’ll add code to re-implement themenus using actions Get your definition of the SketchFrameclass to the following state:

// Frame for the Sketcher applicationimport javax.swing.JMenu;

import static java.awt.event.InputEvent.*;

import static java.awt.AWTEvent.*;

import static java.awt.Color.*;

import static Constants.SketcherConstants.*;

public class SketchFrame extends JFrame {// Constructor

public SketchFrame(String title) {setTitle(title); // Set the window titlesetJMenuBar(menuBar); // Add the menu bar to the windowsetDefaultCloseOperation(EXIT_ON_CLOSE); // Default is exit the applicationJMenu fileMenu = new JMenu(“File”); // Create File menu

JMenu elementMenu = new JMenu(“Elements”); // Create Elements menufileMenu.setMnemonic(‘F’); // Create shortcutelementMenu.setMnemonic(‘E’); // Create shortcut// You will construct the file drop-down menu here using actions

// you will add the types menu items here using actions

elementMenu.addSeparator();

JMenu colorMenu = new JMenu(“Color”); // Color sub-menuelementMenu.add(colorMenu); // Add the sub-menu

Handling Events

Trang 36

// You will add the color menu items here using actions

menuBar.add(fileMenu); // Add the file menumenuBar.add(elementMenu); // Add the element menu}

// You will add inner classes defining action objects here

// You will add action objects as members here

private JMenuBar menuBar = new JMenuBar(); // Window menu bar

private Color elementColor = DEFAULT_ELEMENT_COLOR; // Current element colorprivate int elementType = DEFAULT_ELEMENT_TYPE; // Current element type}

Note that you have restored the statement to set the default close operation as EXIT_ON_CLOSE, so youwon’t need to call dispose()and exit()in the window event handler Now would be a good time todelete the statements from the windowClosing()method in the inner WindowHandlerclass to theSketcherclass The old inner classes in SketchFramehave been deleted, as well as the fields storing ref-erences to menu items All the code to create the menu items has been wiped as well, along with the codethat added the listeners You are ready to begin reconstruction You can rebuild it stronger, faster, better!Defining Action Classes

You’ll need three inner classes defining actions, one for the Filemenu items, another for the

element type menu items, and the third for element colors You’ll derive each of these classes fromthejavax.swing.AbstractActionclass that already implements the Actioninterface The

AbstractActionclass has three constructors:

iconAbstractAction(String name) Defines an object with the name specified by

the argument and a default iconAbstractAction(String name, Icon icon) Defines an object with the name and icon

specified by the arguments

The AbstractActionclass definition already provides the mechanism for storing action properties Forthe last two constructors, the argument values that you pass will be stored using the standard keys that Idescribed earlier in the context of the Actioninterface For the moment, you’ll take advantage only ofthe second constructor and leave icons till a little later

You can define the FileActioninner class as follows:

// Inner class defining Action objects for File menu items

class FileAction extends AbstractAction {

// ConstructorFileAction(String name) {

Chapter 18

Trang 37

}// ConstructorFileAction(String name, KeyStroke keystroke) {this(name);

if(keystroke != null) {putValue(ACCELERATOR_KEY, keystroke);

}}// Event handlerpublic void actionPerformed(ActionEvent e) {// You will add action code here eventually

}}You have two constructors The first just stores the name for the action by calling the base class constructor.The second stores the name by calling the first constructor and then stores the accelerator keystroke usingthe appropriate key if the argument is not null Calling the other constructor rather than the base classconstructor is better here, in case you add code to the other constructor later on (as you certainly will!).Because the class is an action listener, you have implemented the actionPerformed()method in it.You don’t yet know what you are going to do with the File menu item actions, so you can leave it openfor now and let the actionPerformed()method do nothing Add the FileActionclass as an innerclass to SketchFramewhere the comment indicated

The SketchFrameclass will need a data member of type FileActionfor each menu item you intend toadd, so add the following statement to the SketchFrameclass definition where the comment indicated:// File actions

private FileAction newAction, openAction, closeAction,

saveAction, saveAsAction, printAction;

You can define an inner class for the element type menus next:

// Inner class defining Action objects for Element type menu itemsclass TypeAction extends AbstractAction {

TypeAction(String name, int typeID) {super(name);

this.typeID = typeID;

}public void actionPerformed(ActionEvent e) {elementType = typeID;

}private int typeID;

}Add this definition to the SketchFrameclass following the previous inner class The only extra codehere compared to the previous action class is that you retain the typeIDconcept to identify the element

Handling Events

Trang 38

type This makes the listener operation simple and fast Because each object corresponds to a particularelement type, there is no need for any testing of the event — you just store the current typeIDas the newelement type in the SketchFrameclass object You won’t be adding accelerator key combinations fortype menu items, so you don’t need to provide for them in the class.

Add the following statement to the SketchFrameclass for the members that will store references to theTypeActionobjects, following the statement defining the fields that store FileActionreferences:// Element type actions

private TypeAction lineAction, rectangleAction, circleAction, curveAction;

The third inner class to SketchFrameis just as simple:

// Handles color menu items

class ColorAction extends AbstractAction {

public ColorAction(String name, Color color) {super(name);

this.color = color;

}public void actionPerformed(ActionEvent e) {elementColor = color;

// This is temporary – just to show it works

getContentPane().setBackground(color);

}private Color color;

}

You also use the same idea that you used in the listener class for the Colormenu items in the previousimplementation of SketchFrame Here you have a statement in the actionPerformed()method thatsets the background color of the content pane to the element color When you click on a color menu item,the background color of the content pane will change so you will be able to see that it works You’llremove this code later

Add the following statement to the SketchFrameclass for the color action members following theTypeActionfields:

// Element color actions

private ColorAction redAction, yellowAction,

greenAction, blueAction;

You can try these action classes out now

Try It Out Actions in Action

Fundamentally, all you need to do to create the menu items is use the JMenuItemconstructor, whichaccepts an Actionargument, and then use the add()method for the JMenuobject to add the menu item

to a menu This all happens in the SketchFrameconstructor — with the aid of a helper method that willeconomize on the number of lines of code you need:

Chapter 18

Trang 39

color menus You create the action items for these menus in the arguments to the JMenuItemconstructorcalls It’s convenient to do this here as the constructor calls are relatively simple You could adopt thesame approach with the File menu items, but the statements will begin to look rather complicated.When you create a JMenuItemobject from an Actionobject, the accelerator key combination is auto-matically set for the menu item when the Actionobject defines one.

If you recompile and run Sketcher, you will get a window that looks like the one shown in Figure 18-7

Figure 18-7

How It Works

You create an Actionobject for each item in the File menu You then create the menu items ing to the Actionobjects and add them to the File menu The JMenuItemconstructor automaticallyadds an accelerator key for a menu item if it exists in the Actionobject

correspond-The items for the other menus are created in essentially the same way except that you create and storethe Actionobjects in the expressions that produce the arguments to the JMenuItemconstructor calls.These objects are then passed to the add()method for the menu object It’s reasonable to create theActionobjects in the expressions for the arguments to the constructor, as they are relatively simpleexpressions Because you store the references to the Actionobjects, they will be available later whenyou want to create toolbar buttons corresponding to the menu items The accelerators for the Elementsmenu items have been omitted here on the grounds that they were not exactly standard or convenient

If you try out the color menus you should see the background color change If it doesn’t, there’s thing wrong somewhere Now that you have the menus set up using Actionobjects, you are ready totackle adding a toolbar to the Sketcher application

some-Chapter 18

Trang 40

public SketchFrame(String title) {setTitle(title); // Set the window titlesetJMenuBar(menuBar); // Add the menu bar to the windowsetDefaultCloseOperation(EXIT_ON_CLOSE); // Default is exit the applicationJMenu fileMenu = new JMenu(“File”); // Create File menu

JMenu elementMenu = new JMenu(“Elements”); // Create Elements menufileMenu.setMnemonic(‘F’); // Create shortcutelementMenu.setMnemonic(‘E’); // Create shortcut// Create the action items for the file menu

newAction = new FileAction(“New”, KeyStroke.getKeyStroke(‘N’, CTRL_DOWN_MASK));openAction = new FileAction(“Open”,

KeyStroke.getKeyStroke(‘O’, CTRL_DOWN_MASK));closeAction = new FileAction(“Close”);

saveAction = new FileAction(“Save”,

KeyStroke.getKeyStroke(‘S’, CTRL_DOWN_MASK));saveAsAction = new FileAction(“Save As ”);

printAction = new FileAction(“Print”,

KeyStroke.getKeyStroke(‘P’, CTRL_DOWN_MASK));// Construct the file drop-down menu

new TypeAction(“Rectangle”, RECTANGLE)));elementMenu.add(new JMenuItem(circleAction =

new TypeAction(“Circle”, CIRCLE)));elementMenu.add(new JMenuItem(curveAction = new TypeAction(“Curve”, CURVE)));elementMenu.addSeparator();

JMenu colorMenu = new JMenu(“Color”); // Color sub-menuelementMenu.add(colorMenu); // Add the sub-menucolorMenu.add(new JMenuItem(redAction = new ColorAction(“Red”, RED)));

colorMenu.add(new JMenuItem(yellowAction = new ColorAction(“Yellow”, YELLOW)));colorMenu.add(new JMenuItem(greenAction = new ColorAction(“Green”, GREEN)));colorMenu.add(new JMenuItem(blueAction = new ColorAction(“Blue”, BLUE)));

menuBar.add(fileMenu); // Add the file menumenuBar.add(elementMenu); // Add the element menu}

You have added four blocks of code The first two are for the File menu, one block creating the actionobjects and the other creating the menu items The other two blocks of code are for the element type and

Handling Events

Ngày đăng: 13/08/2014, 18:20

TỪ KHÓA LIÊN QUAN