Implementing Simple GUIs in Java
11. Each registered listener performs the steps in its implementation of actionPerformed 6.2.3—
Examples of Events -Handling Options in the Java 2 Platform
A number of options were described in Section 6.2.1 for implementation of the details for event handling. In this section we define a simple GUI application consisting of a frame with two buttons and a label in it. Then we present details for all the available options for handling events generated by this simple application. A screen shot is shown in Figure 6.4 of the application after it is first launched and after a number of button clicks. The name of our application class is
EventExampleUI.
We need to handle two kinds of events. The first is an ActionEvent generated by one of the buttons. The second is a
Figure 6.4. Simple event example in Java.
''close" icon (with an × on it) in the upper right corner of the frame. This allows us to close the frame and application. We begin by letting the UI class handle the action event and delegating the window-closing event to an anonymous adapter class. We will then look at other options for handling the action events.
6.2.3.1—
Handling the WindowEvent with an Anonymous Adapter Class
Anonymous classes are classes with no specific name that implement an interface or extend an existing class.
Anonymous classes can only be inner classes. Therefore, we extend and redefine part of the WindowAdapter class (the method for window closing) as an inner class within our application class, EventExampleUI. Listing 6.4 shows the details for this part of EventExampleUI.java. It is contained in the implementation details for the constructor and shown in boldface in Listing 6.4. Class EventExampleUI extends class java.awt.Frame.
Listing 6.4 Details for an Anonymous Window Adapter Inner Class
// constructor
public EventExampleUI() { super("An Incrementer" );
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}});
initialize(); }
We are adding a WindowListener to the current object (an instance of EventExampleUI) that is created as a new
WindowAdapter subclass that redefines the windowClosing command to exit (close the application).
When we compile EventExampleUI we get two .class files. The first is EventExampleUI.class as expected; the second is the compiled version of the anonymous inner class. By default, the Java compiler assigns unique labels to all anonymous classes. In this example, we will observe a class file EventExampleUI$1.class. The $ sign attaches the anonymous class designation (1 in this case) to its containing class (EventExampleUI). In this way Java maintains unique names for all classes including named or anonymous inner classes.
TEAM
FLY
Since only method windowClosing is redefined in our anonymous inner class, we accept the default (null)
implementations in WindowAdapter for the remaining six methods of interface WindowListener. Without the short code segment just described, our application cannot be closed gracefully. The only options would be to use a system interrupt such as the task manager or a control key from within a command window (or of course the time-tested method of pulling the plug out of the wall!).
6.2.3.2—
Handling an Action Event – Letting the Application Implement ActionListener
As a first option for handling action events we let the application class implement the ActionListener interface. This is a promise to implement the method defined in ActionListener, which is actionPerformed(ActionEvent evt). Complete details for class EventExampleUI are given in Listing 6.5. We see the details of the event-handling code as well as details for building the user interface. Statements necessary for handling action events are shown in boldface in the listing.
Listing 6.5 Complete Details for EventExampleUI.java
import java.awt.*; import java.awt.event.*;
public class EventExampleUI extends Frame implements ActionListener {
Label valueLabel; Button incrementButton; Button resetButton;
public EventExampleUI () { super (''An Incrementer" );
addWindowListener(new WindowAdapter () { public void windowClosing(WindowEvent evt) System.exit(0);}});
initialize(); }
private void initialize () { setSize(200, 100);
this.setLayout(null);
incrementButton = new Button("Increment" ); incrementButton.setBounds(20, 40, 75, 25); // register instance of this class as a listener incrementButton.addActionListener(this);
add(incrementButton);
resetButton = new Button("Reset" ); resetButton.setBounds(105, 40, 75, 25);
resetButton.addActionListener(this);
add(resetButton);
valueLabel = new Label(''0" , Label.CENTER); valueLabel.setBounds(75, 70, 50, 25);
add(valueLabel); setVisible(true); }
public void actionPerformed (ActionEvent evt) {
if (evt.getSource() == incrementButton) {
int value = (new Integer(valueLabel.getText())).intValue();
valueLabel.setText(String.valueOf(value + 1));
} else if (evt.getSource() == resetButton)
valueLabel.setText("0");
}
public static void main (String[] args) { new EventExampleUI();
} }
Three significant things must happen for EventExampleUI to handle the events generated by the two buttons. 1. Class EventExampleUI must promise that it implements ActionListener.
2. Class EventExampleUI must register itself (this) with both buttons to receive notification of the action events they generate. This is achieved by sending the message addActionListener(this) to each button.
3. Class EventExampleUI must implement method actionPerformed from interface ActionListener to provide its intended response. Details of the response are shown in the listing, either incrementing the valueLabel by one or resetting it to zero.
6.2.3.3—
Handling an Action Event – Using an Anonymous Inner Class
Listing 6.6 shows significant parts of a revised version of EventExampleUI2.java that uses anonymous inner classes to handle action events from the two buttons.
Listing 6.6 Using an Anonymous Inner Class to Handle Action Events
import java.awt.*; import java.awt.event.*;
public class EventExampleUI2 extends Frame { . . .
private void initialize () { setSize(200, 100);
this.setLayout(null);
incrementButton = new Button(''Increment" ); incrementButton.setBounds(20, 40, 75, 25);
incrementButton.addActionListener( new ActionListener() {
public void actionPerformed (ActionEvent evt) {
int value = (new
Integer(valueLabel.getText())).intValue();
valueLabel.setText(String.valueOf(value + 1));
}});
add(incrementButton);
resetButton = new Button("Reset" ); resetButton.setBounds(105, 40, 75, 25);
resetButton.addActionListener( new ActionListener() {
public void actionPerformed (ActionEvent evt) {
valueLabel.setText("0"); }}); . . . } . . . }
First, notice that class EventExampleUI2 does not say it implements ActionListener and that method actionPerformed is removed as a separate method. The code shown in boldface creates and implements two anonymous inner classes to handle action events from each of the buttons.
When file EventExampleUI2.java is compiled it produces four .class files: EventexampleUI2.class, EventExampleUI2 $1.class, EventExampleUI2$2.class, and EventExampleUI2$3.class.
6.2.3.4—
Handling an Action Event – Using a Named Inner Class
This option is similar to the anonymous inner class except it looks more like our "normal" definition of classes, creation of objects and message sending. Class EventExampleUI3 adds an action listener to incrementButton that is a new instance of the named inner class IncrementButtonEventHandler. The named inner class has one responsibility – to implement actionPerformed for the incrementButton action event. Selected details are shown in Listing 6.7 for this option.
Listing 6.7 Using a Named Inner Class to Handle incrementButton Events
import java.awt.*; import java.awt.event.*;
public class EventExampleUI3 extends Frame { . . .
private void initialize () { setSize(200, 100);
this.setLayout(null);
incrementButton = new Button(''Increment" ); incrementButton.setBounds(20, 40, 75, 25); incrementButton.addActionListener( new IncrementButtonEventHandler()); add(incrementButton); . . . }
class IncrementButtonEventHandler implements ActionListener {
public void actionPerformed (ActionEvent evt) {
int value = (new
Integer(valueLabel.getText())).intValue(); valueLabel.setText(String.valueOf(value + 1)); } } . . . }
When file EventExampleUI3.java is compiled it produces for the named inner class a file named EventExampleUI3 $IncrementButtonEventHandler.class. In other words, it is consistent with the naming convention for all inner classes. It just happens to have a specific name instead of a number.
6.2.3.5—
Handling an Action Event – Using an External Helper Class
The major disadvantage of an external helper class when compared to the inner classes is that it does not have direct access to the fields of the UI class. Since the event handler method typically needs to interact with those fields, this is a distinct disadvantage. There are several ways to handle this problem. One approach is to let the helper class have a field that is a reference to the UI class. Being in the same package makes direct access to all but private fields of the UI class possible through this contained field. This approach is illustrated in Listing 6.8 for EventExampleUI4 with external helper class IncrementButtonEventHandler.
Listing 6.8 Using an External Helper Class for Handling incrementButton Events
import java.awt.*; import java.awt.event.*;
public class EventExampleUI4 extends Frame { . . .
private void initialize () { setSize(200, 100);
this.setLayout(null);
incrementButton = new Button(''Increment" ); incrementButton.setBounds(20, 40, 75, 25); incrementButton.addActionListener( new IncrementButtonEventHandler(this)); add(incrementButton); . . . } . . . }
class IncrementButtonEventHandler implements ActionListener {
EventExampleUI4 frame;
public IncrementButtonEventHandler(EventExampleUI4 f) {
frame = f;
}
public void actionPerformed (ActionEvent evt) {
int value = (new
Integer(frame.valueLabel.getText())).intValue();
frame.valueLabel.setText(String.valueOf(value + 1));
} }
As a separate class, IncrementButtonEventHandler.java compiles to IncrementButtonEventHandler.class.
We have presented a number of options for implementing the event handling part of a GUI application. All are correct and work as expected. The choice of one option over another is often dependent on the attitudes of the implementer. A number of popular Java development environments all make different choices. We prefer the anonymous inner class that either implements the listener interface or extends the appropriate adapter class (JBuilder also makes this choice). This approach offers the advantage that the response to an event is easily located with the other code that initializes the event source. In other words, we can see the intended response to a button click in the same group of code that sets its size and other properties.
6.3—
Implementing MVC in Java
In this section we develop a simple MVC example where the model is a simple counter. The controller includes two buttons to allow the user to increment or reset the counter. The view is a simple text label showing the current value of the
Figure 6.5.
A simple MVC GUI application.
counter. This example is implemented using both the inheritance approach and the delegation (beans) approach. Figure 6.5 shows the GUI (bean version) for this application. The inheritance version is identical (in both look and feel) except for its title.
6.3.1—
MVC Counter Example Using the Inheritance Approach
The design for this simple example is shown in Figure 6.6. The model class, CounterModel, extends Observable and the view-controller class, CounterUI, implements interfaces Observer and ActionListener. Recall from Chapter 5 that class
Observable represents models and interface Observer represents view controllers in the inheritance approach to MVC. Listing 6.9 shows all details of the CounterModel class. Key method setCount(anInt) changes the internal state and has responsibility for broadcasting any change notifications using inherited methods, setChanged() and notifyObservers(), from parent class Observable.
Figure 6.6.
Listing 6.9 Details of Class CounterModel
/* class CounterModel - MVC Inheritance Example */
import java.util.*;
public class CounterModel extends Observable {
protected int count = 0; // commands
public void setCount (int anInt) {
count = anInt; setChanged(); notifyObservers(); }
public void increment () {
setCount(count + 1); }
public void reset () {
setCount(0); }
// queries
public int getCount () {
return count; }
}
Listing 6.10 shows selected details of class CounterUI. The implementation for method actionPerformed shows how the controller function interacts with the model. Method update (from interface Observer) shows how the view responds to an update message from the model; it queries the model for its count and updates the labelCount widget in the GUI. The GUI class must register as an observer of the model and a listener for the button events.
Listing 6.10 Selected Details of Class CounterUI Showing View-Controller Methods
import java.awt.event.*; import java.awt.*; import java.util.*; import javax.swing.*;
public class CounterUI extends JFrame
implements Observer, ActionListener {
. . .
// set a reference to the model
protected CounterModel counter = new CounterModel(); . . .
public void init () {
. . .
// register with model and controls counter.addObserver(this); incrementButton.addActionListener(this); resetButton.addActionListener(this); . . . } . . .
// View update method
public void update (Observable o, Object arg) {
if ( o instanceof CounterModel)
labelCount.setText(''Current value of count: " + String.valueOf(counter.getCount())); }
// Controller event handlers
public void actionPerformed (ActionEvent evt) {
if (evt.getSource() == resetButton) counter.reset();
else if (evt.getSource() == incrementButton) counter.increment();
}
/** Invoke this as a stand alone application. */
public static void main (String args[]) {
new CounterUI("MVC-Counter" ); }
}
6.3.2—
MVC Counter Example Using the Delegation (Beans) Approach
The design for this simple example is shown in Figure 6.7. The model class, CounterBean, extends Object and uses a delegate instance of PropertyChangeSupport to notify any views of changes in the state of the model. This is accomplished by
Figure 6.7.
Design for MVC implementation using delegation (beans) approach.
directing the delegate to fire a PropertyChangeEvent. The view-controller class, CounterBeanUI, implements interfaces
PropertyChangeListener for notification of changes in the model and ActionListener for handling controller events. Since the model is using a delegate for its MVC duties, it may extend any class. In this case, it extends Object. The only change in the view-controller class is implementation of a different interface (PropertyChangeListener instead of
Observer) for notification by the model's change monitor.
Listing 6.11 shows details for class CounterBean. The key method setCount must modify the internal state and then fire a PropertyChangeEvent via the delegate object propertyChange. Since the view-controller only has a reference to its model, the model must also include methods allowing a view to register as a listener for the property change event. The model implements two registration methods, for adding and removing property change listeners, by invoking the appropriate similar methods on the delegate.
Listing 6.11 Details of Class CounterBean
/** class CounterBean - MVC Delegation Example */
import java.beans.*;
public class CounterBean extends Object {
protected PropertyChangeSupport propertyChange
= new PropertyChangeSupport(this); protected int count = 0;
// supporting commands for registration of listeners public synchronized void addPropertyChangeListener
(PropertyChangeListener listener) {
propertyChange.addPropertyChangeListener(listener); }
public synchronized void removePropertyChangeListener
(PropertyChangeListener listener) {
propertyChange.removePropertyChangeListener(listener); }
// model commands
public void setCount (int anInt) {
count = anInt;
propertyChange.firePropertyChange(''count" , null, null); }
public void increment () {
setCount(count + 1); }
public void reset () {
setCount(0); }
// queries
public int getCount () {
return count; }
}
Listing 6.12 shows selected details of class CounterBeanUI that are important to the beans approach to MVC.
TEAM
FLY
Listing 6.12 Selected Details of ClassCounterBeanUI
import java.awt.event.*; import java.awt.*; import java.beans.*; import javax.swing.*;
public class CounterBeanUI extends JFrame
implements PropertyChangeListener, ActionListener {
protected CounterBean counter = new CounterBean(); . . .
public void init () {
. . .
// register with model and controller event sources counter.addPropertyChangeListener(this); incrementButton.addActionListener(this); resetButton.addActionListener(this); . . . } . . .
// event handling – for model invoked event
public void propertyChange(PropertyChangeEvent evt) {
if ((evt.getSource() == counter)
&& (evt.getPropertyName().equals(''count" ))) { labelCount.setText("Current value of count: " + String.valueOf(counter.getCount())); }
}
// event handling - controller generated events public void actionPerformed (ActionEvent evt) {
if (evt.getSource() == resetButton) counter.reset();
else if (evt.getSource() == incrementButton) counter.increment();
}
public static void main (String args[]) {
new CounterBeanUI("MVC-Bean Counter" ); }
}
A simple walk through is helpful in understanding this example. When the application is launched the constructor in class CounterBeanUI (not shown in the
listing) invokes the init method, which registers the instance of CounterBeanUI as a listener with the model, counter, and with the two event sources used by the controller, incrementButton and resetButton. Suppose the user clicks the increment button (with label ''Increment"). This causes an ActionEvent to be fired. Having registered as a listener for action events, our application is sent the message actionPerformed. Based on the details of actionPerformed in Listing 6.12, the increment message is sent to model counter. From Listing 6.11 the increment method in class CounterBean
invokes setCount. Method setCount updates the value of count to be one larger than before and tells its propertyChange
delegate to fire a PropertyChangeEvent. From this point we must look at the source code for PropertyChangeSupport to see what happens next. In essence, the propertyChange delegate posts the event and then handles it by sending the message propertyChange to all registered listeners (including our application object – an instance of CounterBeanUI). The steps in method propertyChange in Listing 6.12 are then executed to update the value of the labelCount widget in our application GUI.
There are many options for either approach to MVC in Java. For example, a model may have multiple views and controllers. These views and controllers may reside in one or more GUIs. A given view controller (GUI) may also have multiple models.
6.4— Summary
Implementation of a graphical user interface (GUI) in Java may be done by using appropriate classes in the abstract windowing toolkit (AWT) and Java foundation classes (JFC) directly or by using one of many available interface development environments (IDEs).
The major requirements for a GUI include: 1. a top-level container (instance of Frame or