1. Introduction
So far, we have covered the basic programming constructs (such as variables, data types, decision, loop, array and method) and introduced the important concept of Object-Oriented Programming (OOP). As discussed, OOP permits higher level of abstraction than traditional Procedural-Oriented languages (such as C and Pascal). You can create high-level abstract data types called classes to mimic real-life things. These classes are self-contained and are reusable.
In this article, I shall show you how you can reuse the graphics classes provided in JDK for constructing your own Graphical User Interface (GUI) applications. Writing your own graphics classes (and re-inventing the wheels) is mission impossible! These graphics classes, developed by expert programmers, are highly complex and involve many advanced design patterns. However, re-using them are not so difficult, if you follow the API documentation, samples and templates provided.
I shall assume that you have a good grasp of OOP, including composition, inheritance, polymorphism, abstract class and interface; otherwise, read the earlier articles. I will describe another important OO concept called nested class (or inner class) in this article.
There are current three sets of Java APIs for graphics programming: AWT (Abstract Windowing Toolkit), Swing and JavaFX.
- AWT API was introduced in JDK 1.0. Most of the AWT components have become obsolete and should be replaced by newer Swing components.
- Swing API, a much more comprehensive set of graphics libraries that enhances the AWT, was introduced as part of Java Foundation Classes (JFC) after the release of JDK 1.1. JFC consists of Swing, Java2D, Accessibility, Internationalization, and Pluggable Look-and-Feel Support APIs. JFC has been integrated into core Java since JDK 1.2.
- The latest JavaFX, which was integrated into JDK 8, is meant to replace Swing.
Other than AWT/Swing/JavaFX graphics APIs provided in JDK, other organizations/vendors have also provided graphics APIs that work with Java, such as Eclipse's Standard Widget Toolkit (SWT) (used in Eclipse), Google Web Toolkit (GWT) (used in Android), 3D Graphics API such as Java bindings for OpenGL (JOGL) and Java3D.
You need to check the JDK 9 API (https://docs.oracle.com/javase/9/docs/api/overview-summary.html) for the AWT/Swing APIs (under module java.desktop) and JavaFX (under modules javafx.*) while reading this chapter. The best online reference for Graphics programming is the "Swing Tutorial" @ http://docs.oracle.com/javase/tutorial/uiswing/. For advanced 2D graphics programming, read "Java 2D Tutorial" @ http://docs.oracle.com/javase/tutorial/2d/index.html. For 3D graphics, read my 3D articles.
2. Programming GUI with AWT
I shall start with the AWT before moving into Swing to give you a complete picture of Java Graphics.
2.1 AWT Packages
AWT is huge! It consists of 12 packages of 370 classes (Swing is even bigger, with 18 packages of 737 classes as of JDK 8). Fortunately, only 2 packages - java.awt and java.awt.event - are commonly-used.
- The
java.awt package contains the core AWT graphics classes:
- GUI Component classes, such as
Button, TextField, and Label.
- GUI Container classes, such as
Frame and Panel.
- Layout managers, such as
FlowLayout, BorderLayout and GridLayout.
- Custom graphics classes, such as
Graphics, Color and Font.
- The
java.awt.event package supports event handling:
- Event classes, such as
ActionEvent, MouseEvent, KeyEvent and WindowEvent,
- Event Listener Interfaces, such as
ActionListener, MouseListener, MouseMotionListener, KeyListener and WindowListener,
- Event Listener Adapter classes, such as
MouseAdapter, KeyAdapter, and WindowAdapter.
AWT provides a platform-independent and device-independent interface to develop graphic programs that runs on all platforms, including Windows, Mac OS X, and Unixes.
2.2 Containers and Components
There are two types of GUI elements:
- Component: Components are elementary GUI entities, such as
Button, Label, and TextField.
- Container: Containers, such as
Frame and Panel, are used to hold components in a specific layout (such as FlowLayout or GridLayout). A container can also hold sub-containers.
In the above figure, there are three containers: a Frame and two Panels. A Frame is the top-level container of an AWT program. A Frame has a title bar (containing an icon, a title, and the minimize/maximize/close buttons), an optional menu bar and the content display area. A Panel is a rectangular area used to group related GUI components in a certain layout. In the above figure, the top-level Frame contains two Panels. There are five components: a Label (providing description), a TextField (for users to enter text), and three Buttons (for user to trigger certain programmed actions).
In a GUI program, a component must be kept in a container. You need to identify a container to hold the components. Every container has a method called add(Component c). A container (say c) can invoke c.add(aComponent) to add aComponent into itself. For example,
Panel pnl = new Panel(); // Panel is a container
Button btn = new Button("Press"); // Button is a component
pnl.add(btn); // The Panel container adds a Button component
GUI components are also called controls (e.g., Microsoft ActiveX Control), widgets (e.g., Eclipse's Standard Widget Toolkit, Google Web Toolkit), which allow users to interact with (or control) the application.
2.3 AWT Container Classes
Top-Level Containers: Frame, Dialog and Applet
Each GUI program has a top-level container. The commonly-used top-level containers in AWT are Frame, Dialog and Applet:
A Frame provides the "main window" for your GUI application. It has a title bar (containing an icon, a title, the minimize, maximize/restore-down and close buttons), an optional menu bar, and the content display area. To write a GUI program, we typically start with a subclass extending from java.awt.Frame to inherit the main window as follows:
import java.awt.Frame; // Using Frame class in package java.awt
// A GUI program is written as a subclass of Frame - the top-level container
// This subclass inherits all properties from Frame, e.g., title, icon, buttons, content-pane
public class MyGUIProgram extends Frame {
// private variables
......
// Constructor to setup the GUI components
public MyGUIProgram() { ...... }
// methods
......
......
// The entry main() method
public static void main(String[] args) {
// Invoke the constructor (to setup the GUI) by allocating an instance
new MyGUIProgram();
}
}
An AWT Dialog is a "pop-up window" used for interacting with the users. A Dialog has a title-bar (containing an icon, a title and a close button) and a content display area, as illustrated.
- An AWT
Applet (in package java.applet) is the top-level container for an applet, which is a Java program running inside a browser.
Secondary Containers: Panel and ScrollPane
Secondary containers are placed inside a top-level container or another secondary container. AWT provides these secondary containers:
Panel: a rectangular box used to layout a set of related GUI components in pattern such as grid or flow.
ScrollPane: provides automatic horizontal and/or vertical scrolling for a single child component.
- others.
Hierarchy of the AWT Container Classes
The hierarchy of the AWT Container classes is as follows:
As illustrated, a Container has a LayoutManager to layout the components in a certain pattern.
2.4 AWT Component Classes
AWT provides many ready-made and reusable GUI components in package java.awt. The frequently-used are: Button, TextField, Label, Checkbox, CheckboxGroup (radio buttons), List, and Choice, as illustrated below.
AWT GUI Component: java.awt.Label
A java.awt.Label provides a descriptive text string. Take note that System.out.println() prints to the system console, NOT to the graphics screen. You could use a Label to label another component (such as text field) to provide a text description.
Check the JDK API specification for java.awt.Label.
Constructors
public Label(String strLabel, int alignment); // Construct a Label with the given text String, of the text alignment
public Label(String strLabel); // Construct a Label with the given text String
public Label(); // Construct an initially empty Label
The Label class has three constructors:
- The first constructor constructs a
Label object with the given text string in the given alignment. Note that three static constants Label.LEFT, Label.RIGHT, and Label.CENTER are defined in the class for you to specify the alignment (rather than asking you to memorize arbitrary integer values).
- The second constructor constructs a
Label object with the given text string in default of left-aligned.
- The third constructor constructs a
Label object with an initially empty string. You could set the label text via the setText() method later.
Constants (final static fields)
public static final LEFT; // Label.LEFT
public static final RIGHT; // Label.RIGHT
public static final CENTER; // Label.CENTER
These three constants are defined for specifying the alignment of the Label's text, as used in the above constructor.
Public Methods
// Examples
public String getText();
public void setText(String strLabel);
public int getAlignment();
public void setAlignment(int alignment); // Label.LEFT, Label.RIGHT, Label.CENTER
The getText() and setText() methods can be used to read and modify the Label's text. Similarly, the getAlignment() and setAlignment() methods can be used to retrieve and modify the alignment of the text.
Constructing a Component and Adding the Component into a Container
Three steps are necessary to create and place a GUI component:
- Declare the component with an identifier (name);
- Construct the component by invoking an appropriate constructor via the
new operator;
- Identify the container (such as
Frame or Panel) designed to hold this component. The container can then add this component onto itself via aContainer.add(aComponent) method. Every container has a add(Component) method. Take note that it is the container that actively and explicitly adds a component onto itself, NOT the other way.
Example
Label lblInput; // Declare an Label instance called lblInput
lblInput = new Label("Enter ID"); // Construct by invoking a constructor via the new operator
add(lblInput); // this.add(lblInput) - "this" is typically a subclass of Frame
lblInput.setText("Enter password"); // Modify the Label's text string
lblInput.getText(); // Retrieve the Label's text string
An Anonymous Instance
You can create a Label without specifying an identifier, called anonymous instance. In the case, the Java compiler will assign an anonymous identifier for the allocated object. You will not be able to reference an anonymous instance in your program after it is created. This is usually alright for a Label instance as there is often no need to reference a Label after it is constructed.
Example
// Allocate an anonymous Label instance.
// "this" container adds the instance.
// You CANNOT reference an anonymous instance to carry out further operations.
add(new Label("Enter Name: ", Label.RIGHT));
// Same as
Label xxx = new Label("Enter Name: ", Label.RIGHT)); // xxx assigned by compiler
add(xxx);
AWT GUI Component: java.awt.Button
A java.awt.Button is a GUI component that triggers a certain programmed action upon clicking.
Constructors
public Button(String btnLabel);
// Construct a Button with the given label
public Button();
// Construct a Button with empty label
The Button class has two constructors. The first constructor creates a Button object with the given label painted over the button. The second constructor creates a Button object with no label.
Public Methods
public String getLabel();
// Get the label of this Button instance
public void setLabel(String btnLabel);
// Set the label of this Button instance
public void setEnable(boolean enable);
// Enable or disable this Button. Disabled Button cannot be clicked.
The getLabel() and setLabel() methods can be used to read the current label and modify the label of a button, respectively.
Note: The latest Swing's JButton replaces getLabel()/setLabel() with getText()/setText() to be consistent with all the components. We will describe Swing later.
Event
Clicking a button fires a so-called ActionEvent and triggers a certain programmed action. I will explain event-handling later.
Example
Button btnColor = new Button("Red"); // Declare and allocate a Button instance called btnColor
add(btnColor); // "this" Container adds the Button
...
btnColor.setLabel("Green"); // Change the button's label
btnColor.getLabel(); // Read the button's label
...
add(Button("Blue")); // Create an anonymous Button. It CANNOT be referenced later
AWT GUI Component: java.awt.TextField
A java.awt.TextField is single-line text box for users to enter texts. (There is a multiple-line text box called TextArea.) Hitting the "ENTER" key on a TextField object fires an ActionEvent.
Constructors
public TextField(String initialText, int columns);
// Construct a TextField instance with the given initial text string with the number of columns.
public TextField(String initialText);
// Construct a TextField instance with the given initial text string.
public TextField(int columns);
// Construct a TextField instance with the number of columns.
Public Methods
public String getText();
// Get the current text on this TextField instance
public void setText(String strText);
// Set the display text on this TextField instance
public void setEditable(boolean editable);
// Set this TextField to editable (read/write) or non-editable (read-only)
Event
Hitting the "ENTER" key on a TextField fires a ActionEvent, and triggers a certain programmed action.
Example
TextField tfInput = new TextField(30); // Declare and allocate an TextField instance called tfInput
add(tfInput); // "this" Container adds the TextField
TextField tfResult = new TextField(); // Declare and allocate an TextField instance called tfResult
tfResult.setEditable(false) ; // Set to read-only
add(tfResult); // "this" Container adds the TextField
......
// Read an int from TextField "tfInput", square it, and display on "tfResult".
// getText() returns a String, need to convert to int
int number = Integer.parseInt(tfInput.getText());
number *= number;
// setText() requires a String, need to convert the int number to String.
tfResult.setText(number + "");
Take note that getText()/SetText() operates on String. You can convert a String to a primitive, such as int or double via static method Integer.parseInt() or Double.parseDouble(). To convert a primitive to a String, simply concatenate the primitive with an empty String.
2.5 Example 1: AWTCounter
Let's assemble a few components together into a simple GUI counter program, as illustrated. It has a top-level container Frame, which contains three components - a Label "Counter", a non-editable TextField to display the current count, and a "Count" Button. The TextField shall display count of 0 initially.
Each time you click the button, the counter's value increases by 1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
import java.awt.*; // Using AWT container and component classes
import java.awt.event.*; // Using AWT event classes and listener interfaces
// An AWT program inherits from the top-level container java.awt.Frame
public class AWTCounter extends Frame implements ActionListener {
private Label lblCount; // Declare a Label component
private TextField tfCount; // Declare a TextField component
private Button btnCount; // Declare a Button component
private int count = 0; // Counter's value
// Constructor to setup GUI components and event handlers
public AWTCounter () {
setLayout(new FlowLayout());
// "super" Frame, which is a Container, sets its layout to FlowLayout to arrange
// the components from left-to-right, and flow to next row from top-to-bottom.
lblCount = new Label("Counter"); // construct the Label component
add(lblCount); // "super" Frame container adds Label component
tfCount = new TextField(count + "", 10); // construct the TextField component with initial text
tfCount.setEditable(false); // set to read-only
add(tfCount); // "super" Frame container adds TextField component
btnCount = new Button("Count"); // construct the Button component
add(btnCount); // "super" Frame container adds Button component
btnCount.addActionListener(this);
// "btnCount" is the source object that fires an ActionEvent when clicked.
// The source add "this" instance as an ActionEvent listener, which provides
// an ActionEvent handler called actionPerformed().
// Clicking "btnCount" invokes actionPerformed().
setTitle("AWT Counter"); // "super" Frame sets its title
setSize(250, 100); // "super" Frame sets its initial window size
// For inspecting the Container/Components objects
// System.out.println(this);
// System.out.println(lblCount);
// System.out.println(tfCount);
// System.out.println(btnCount);
setVisible(true); // "super" Frame shows
// System.out.println(this);
// System.out.println(lblCount);
// System.out.println(tfCount);
// System.out.println(btnCount);
}
// The entry main() method
public static void main(String[] args) {
// Invoke the constructor to setup the GUI, by allocating an instance
AWTCounter app = new AWTCounter();
// or simply "new AWTCounter();" for an anonymous instance
}
// ActionEvent handler - Called back upon button-click.
@Override
public void actionPerformed(ActionEvent evt) {
++count; // Increase the counter value
// Display the counter value on the TextField tfCount
tfCount.setText(count + ""); // Convert int to String
}
}
|
To exit this program, you have to close the CMD-shell (or press "control-c" on the CMD console); or push the "red" close button in Eclipse's Application Console. This is because we have yet to write the handler for the Frame's close button. We shall do that in the later example.
Dissecting the AWTCounter.java
- The
import statements (Lines 1-2) are needed, as AWT container and component classes, such as Frame, Button, TextField, and Label, are kept in the java.awt package; while AWT events and event-listener interfaces, such as ActionEvent and ActionListener are kept in the java.awt.event package.
- A GUI program needs a top-level container, and is often written as a subclass of
Frame (Line 5). In other words, this class AWTCounter is a Frame, and inherits all the attributes and behaviors of a Frame, such as the title bar and content pane.
- Lines 12 to 46 define a constructor, which is used to setup and initialize the GUI components.
- In Line 13, the
setLayout() (inherited from the superclass Frame) is used to set the layout of the container. FlowLayout is used which arranges the components in left-to-right and flows into next row in a top-to-bottom manner.
- A
Label, TextField (non-editable), and Button are constructed. We invoke the add() method (inherited from the superclass Frame) to add these components into container.
- In Line 33-34, we invoke the
setSize() and the setTitle() (inherited from the superclass Frame) to set the initial size and the title of the Frame. The setVisible(true) method (Line 42) is then invoked to show the display.
- The statement
btnCount.addActionListener(this) (Line 27) is used to setup the event-handling mechanism, which will be discussed in length later. In brief, whenever the button is clicked, the actionPerformed() will be called. In the actionPerformed() (Lines 57-63), the counter value increases by 1 and displayed on the TextField.
- In the entry
main() method (Lines 51-55), an instance of AWTCounter is constructed. The constructor is executed to initialize the GUI components and setup the event-handling mechanism. The GUI program then waits for the user input.
Inspecting Container/Components via toString()
It is interesting to inspect the GUI objects via the toString(), to gain an insight to these classes. (Alternatively, use a graphic debugger in Eclipse/NetBeans or study the JDK source code.) For example, if we insert the following code before and after the setvisible():
System.out.println(this);
System.out.println(lblCount);
System.out.println(tfCount);
System.out.println(btnCount);
setVisible(true); // "super" Frame shows
System.out.println(this);
System.out.println(lblCount);
System.out.println(tfCount);
System.out.println(btnCount);
The output (with my comments) are as follows. You could have an insight of the variables defined in the class.
// Before setVisible()
AWTCounter[frame0,0,0,250x100,invalid,hidden,layout=java.awt.FlowLayout,title=AWT Counter,resizable,normal]
// name (assigned by compiler) is "frame0"; top-left (x,y) at (0,0); width/height is 250x100 (via setSize());
java.awt.Label[label0,0,0,0x0,invalid,align=left,text=Counter]
// name is "Label0"; align is "Label.LEFT" (default); text is "Counter" (assigned in constructor)
java.awt.TextField[textfield0,0,0,0x0,invalid,text=0,selection=0-0]
// name is "Textfield0"; text is "0" (assigned in constructor)
java.awt.Button[button0,0,0,0x0,invalid,label=Count]
// name is "button0"; label text is "Count" (assigned in constructor)
// Before setVisible(), all components are invalid (top-left (x,y), width/height are invalid)
// After setVisible(), all components are valid
AWTCounter[frame0,0,0,250x100,layout=java.awt.FlowLayout,title=AWT Counter,resizable,normal]
// valid and visible (not hidden)
java.awt.Label[label0,20,41,58x23,align=left,text=Counter]
// Top-left (x,y) at (20,41) relative to the parent Frame; width/height = 58x23
java.awt.TextField[textfield0,83,41,94x23,text=0,selection=0-0]
// Top-left (x,y) at (83,41) relative to the parent Frame; width/height = 94x23; no text selected (0-0)
java.awt.Button[button0,182,41,47x23,label=Count]
// Top-left (x,y) at (182,41) relative to the parent Frame; width/height = 47x23
2.6 Example 2: AWTAccumulator
In this example, the top-level container is again the typical java.awt.Frame. It contains 4 components: a Label "Enter an Integer", a TextField for accepting user input, another Label "The Accumulated Sum is", and another non-editable TextField for displaying the sum. The components are arranged in FlowLayout.
The program shall accumulate the number entered into the input TextField and display the sum in the output TextField.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
import java.awt.*; // Using AWT container and component classes
import java.awt.event.*; // Using AWT event classes and listener interfaces
// An AWT GUI program inherits from the top-level container java.awt.Frame
public class AWTAccumulator extends Frame implements ActionListener {
private Label lblInput; // Declare input Label
private Label lblOutput; // Declare output Label
private TextField tfInput; // Declare input TextField
private TextField tfOutput; // Declare output TextField
private int sum = 0; // Accumulated sum, init to 0
// Constructor to setup the GUI components and event handlers
public AWTAccumulator() {
setLayout(new FlowLayout());
// "super" Frame (container) sets layout to FlowLayout, which arranges
// the components from left-to-right, and flow to next row from top-to-bottom.
lblInput = new Label("Enter an Integer: "); // Construct Label
add(lblInput); // "super" Frame container adds Label component
tfInput = new TextField(10); // Construct TextField
add(tfInput); // "super" Frame adds TextField
tfInput.addActionListener(this);
// "tfInput" is the source object that fires an ActionEvent upon entered.
// The source add "this" instance as an ActionEvent listener, which provides
// an ActionEvent handler called actionPerformed().
// Hitting "enter" on tfInput invokes actionPerformed().
lblOutput = new Label("The Accumulated Sum is: "); // allocate Label
add(lblOutput); // "super" Frame adds Label
tfOutput = new TextField(10); // allocate TextField
tfOutput.setEditable(false); // read-only
add(tfOutput); // "super" Frame adds TextField
setTitle("AWT Accumulator"); // "super" Frame sets title
setSize(350, 120); // "super" Frame sets initial window size
setVisible(true); // "super" Frame shows
}
// The entry main() method
public static void main(String[] args) {
// Invoke the constructor to setup the GUI, by allocating an anonymous instance
new AWTAccumulator();
}
// ActionEvent handler - Called back upon hitting "enter" key on TextField
@Override
public void actionPerformed(ActionEvent evt) {
// Get the String entered into the TextField tfInput, convert to int
int numberIn = Integer.parseInt(tfInput.getText());
sum += numberIn; // Accumulate numbers entered into sum
tfInput.setText(""); // Clear input TextField
tfOutput.setText(sum + ""); // Display sum on the output TextField
// convert int to String
}
}
|
Dissecting the AWTAccumulator.java
- An AWT GUI program extends from
java.awt.Frame (Line 5) - the top-level window container.
- In the constructor (Line 13), we constructs 4 components - 2
java.awt.Label and 2 java.awt.TextFields. The Frame adds the components, in FlowLayout.
tfInput (TextField) is the source object, which fires an ActionEvent upon hitting the Enter key. tfInput adds this instance as an ActionEvent handler (Line 24). The listener class (this or AWTAccumulator) needs to implement ActionListener interface and provides implementation to method actionPerformed(). Whenever an user hits Enter on the tfInput (TextField), the actionPerformed() will be invoked.
Inspecting Container/Components via toString()
Printing the toString() after setVisible() produces:
AWTAccumulator[frame0,0,0,350x120,layout=java.awt.FlowLayout,title=AWT Accumulator,resizable,normal]
java.awt.Label[label0,72,41,107x23,align=left,text=Enter an Integer: ]
java.awt.Label[label1,47,69,157x23,align=left,text=The Accumulated Sum is: ]
java.awt.TextField[textfield0,184,41,94x23,text=,editable,selection=0-0]
java.awt.TextField[textfield1,209,69,94x23,text=,selection=0-0]
3. AWT Event-Handling
Java adopts the so-called "Event-Driven" (or "Event-Delegation") programming model for event-handling, similar to most of the visual programming languages, such as Visual Basic.
In event-driven programming, a piece of event-handling codes is executed (or called back by the graphics subsystem) when an event was fired in response to an user input (such as clicking a mouse button or hitting the ENTER key in a text field).
Call Back methods
In the above examples, the method actionPerformed() is known as a call back method. In other words, you never invoke actionPerformed() in your codes explicitly. The actionPerformed() is called back by the graphics subsystem under certain circumstances in response to certain user actions.
Source, Event and Listener Objects
The AWT's event-handling classes are kept in package java.awt.event.
Three kinds of objects are involved in the event-handling: a source, listener(s) and an event object.
The source object (such as Button and Textfield) interacts with the user. Upon triggered, the source object creates an event object to capture the action (e.g., mouse-click x and y, texts entered, etc). This event object will be messaged to all the registered listener object(s), and an appropriate event-handler method of the listener(s) is called-back to provide the response. In other words, triggering a source fires an event to all its listener(s), and invoke an appropriate event handler of the listener(s).
To express interest for a certain source's event, the listener(s) must be registered with the source. In other words, the listener(s) "subscribes" to a source's event, and the source "publishes" the event to all its subscribers upon activation. This is known as subscribe-publish or observable-observer design pattern.
The sequence of steps is illustrated above:
- The source object registers its listener(s) for a certain type of event.
A source fires an event when triggered. For example, clicking a Button fires an ActionEvent, clicking a mouse button fires MouseEvent, typing a key fires KeyEvent, and etc.
How the source and listener understand each other? The answer is via an agreed-upon interface. For example, if a source is capable of firing an event called XxxEvent (e.g., MouseEvent) involving various operational modes (e.g., mouse-clicked, mouse-entered, mouse-exited, mouse-pressed, and mouse-released). Firstly, we need to declare an interface called XxxListener (e.g., MouseListener) containing the names of the handler methods (recall that an interface contains only abstract methods without implementation). For example, the MouseListener interface is declared as follows with five operational modes.
// A MouseListener interface, which declares the signature of the handlers
// for the various operational modes.
public interface MouseListener {
public void mousePressed(MouseEvent evt); // Called back upon mouse-button pressed
public void mouseReleased(MouseEvent evt); // Called back upon mouse-button released
public void mouseClicked(MouseEvent evt); // Called back upon mouse-button clicked (pressed and released)
public void mouseEntered(MouseEvent evt); // Called back when mouse pointer entered the component
public void mouseExited(MouseEvent evt); // Called back when mouse pointer exited the component
}
Secondly, all XxxEvent listeners must implement the XxxListener interface. That is, the listeners must provide their own implementations (i.e., programmed responses) to all the abstract methods declared in the XxxListener interface. In this way, the listener(s) can response to these events appropriately. For example,
// An example of MouseListener, which provides implementation to the event handler methods
class MyMouseListener implement MouseListener {
@Override
public void mousePressed(MouseEvent e) {
System.out.println("Mouse-button pressed!");
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse-button released!");
}
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse-button clicked (pressed and released)!");
}
@Override
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse-pointer entered the source component!");
}
@Override
public void mouseExited(MouseEvent e) {
System.out.println("Mouse exited-pointer the source component!");
}
}
Thirdly, in the source, we need to maintain a list of XxxEvent listener object(s), and define two methods: addXxxListener() and removeXxxListener() to add and remove a XxxEvent listener from this list. The signature of the methods are:
public void addXxxListener(XxxListener lis);
public void removeXxxListener(XxxListener lis);
Take note that the addXxxListenser() takes a XxxListener object as its sole parameter. In other words, it can only add objects of the type XxxListener, and its sub-type. Since XxxListener is an interface, you cannot create instance of XxxListener, but need to create instance of a subclass implementing the XxxListener interface.
In summary, we identify the source, the event-listener interface, and the listener object. The listener must implement the event-listener interface. The source object then registers listener object via the addXxxListener(XxxListener lis) method.
- The source is triggered by a user.
- The source create a
XxxEvent object, which encapsulates the necessary information about the activation. For example, the (x, y) position of the mouse pointer, the text entered, etc.
- Finally, for each of the
XxxEvent listeners in the listener list, the source invokes the appropriate handler on the listener(s), which provides the programmed response.
In summary, triggering a source fires an event to all its registered listeners, and invoke an appropriate handler of the listener.
3.1 Revisit Example 1 AWTCounter: ActionEvent and ActionListener Interface
Clicking a Button (or hitting the "Enter" key on a TextField) fires an ActionEvent to all its ActionEvent listener(s). An ActionEvent listener must implement the ActionListener interface, which declares one abstract method called actionPerformed() as follow:
public interface ActionListener {
public void actionPerformed(ActionEvent evt);
// Called back upon button-click (on Button), enter-key pressed (on TextField)
}
Here are the event-handling steps:
- We identify
btnCount (Button) as the source object.
- Clicking
Button fires an ActionEvent to all its ActionEvent listener(s).
- The listener(s) is required to implement
ActionListener interface, and override the actionPerformed() method to provide the response. For simplicity, we choose "this" object (AWTCounter) as the listener for the ActionEvent. Hence, "this" class is required to implement ActionListener interface and provide the programmed response in the actionPerformed().
public class AWTCounter extends Frame implements ActionListener {
// "this" is chosen as the ActionEvent listener, hence, it is required
// to implement ActionListener interface
......
// Implementing ActionListener interface requires this class to provide implementation
// to the abstract method actionPerformed() declared in the interface.
@Override
public void actionPerformed(ActionEvent evt) {
// Programmed response upon activation
// Increment the count value and display on the TextField
++count;
tfCount.setText(count + "");
}
}
- The source object registers listener via the
addActionListener(). In this example, the source btnCount (Button) adds "this" object as a listener via:
btnCount.addActionListener(this);
Note that addActionListener() takes an argument of the type ActionListener. "this", which implements ActionListener interface (i.e., a subclass of ActionListener), is upcasted and passed to the addActionListener() method.
- Upon button-click, the
btnCount creates an ActionEvent object, and calls back the actionPerformed(ActionEvent) method of all its registered listener(s) with the ActionEvent object created:
ActionEvent evt = new ActionEvent( ...... );
listener.actionPerformed(evt); // for all its listener(s)
The sequence diagram is as follows:

3.2 Revisit Example 2 AWTAccumulator: ActionEvent and ActionListener Interface
In this example,
- We identify the
tfInput (TextField) as the source object.
- Hitting the "Enter" key on a
TextField fires an ActionEvent to all its ActionEvent listener(s).
- We choose
this object as the ActionEvent listener (for simplicity).
- The source object
tfInput (TextField) registers the listener (this object) via the tfInput.addActionListener(this).
- The
ActionEvent listener (this class) is required to implement the ActionListener interface, and override the actionPerformed() method to provide the programmed response upon activation.
3.3 Example 3: WindowEvent and WindowListener Interface
A WindowEvent is fired (to all its WindowEvent listeners) when a window (e.g., Frame) has been opened/closed, activated/deactivated, iconified/deiconified via the 3 buttons at the top-right corner or other means. The source of WindowEvent shall be a top-level window-container such as Frame.
A WindowEvent listener must implement WindowListener interface, which declares 7 abstract event-handling methods, as follows. Among them, the windowClosing(), which is called back upon clicking the window-close button, is the most commonly-used.
public void windowClosing(WindowEvent evt)
// Called-back when the user attempts to close the window by clicking the window close button.
// This is the most-frequently used handler.
public void windowOpened(WindowEvent evt)
// Called-back the first time a window is made visible.
public void windowClosed(WindowEvent evt)
// Called-back when a window has been closed as the result of calling dispose on the window.
public void windowActivated(WindowEvent evt)
// Called-back when the Window is set to be the active Window.
public void windowDeactivated(WindowEvent evt)
// Called-back when a Window is no longer the active Window.
public void windowIconified(WindowEvent evt)
// Called-back when a window is changed from a normal to a minimized state.
public void windowDeiconified(WindowEvent evt)
// Called-back when a window is changed from a minimized to a normal state.
The following program added support for "close-window button" to Example 1: AWTCounter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
import java.awt.*; // Using AWT containers and components
import java.awt.event.*; // Using AWT events classes and listener interfaces
// An AWT GUI program inherits the top-level container java.awt.Frame
public class WindowEventDemo extends Frame
implements ActionListener, WindowListener {
// This class acts as listener for ActionEvent and WindowEvent
// A Java class can extend only one superclass, but it can implement multiple interfaces.
private TextField tfCount; // Declare a TextField component
private Button btnCount; // Declare a Button component
private int count = 0; // Counter's value
// Constructor to setup the GUI components and event handlers
public WindowEventDemo() {
setLayout(new FlowLayout()); // "super" Frame sets to FlowLayout
add(new Label("Counter")); // "super" Frame adds an anonymous Label
tfCount = new TextField("0", 10); // Construct the TextField
tfCount.setEditable(false); // read-only
add(tfCount); // "super" Frame adds TextField
btnCount = new Button("Count"); // Construct the Button
add(btnCount); // "super" Frame adds Button
btnCount.addActionListener(this);
// btnCount (source object) fires ActionEvent upon clicking
// btnCount adds "this" object as an ActionEvent listener
addWindowListener(this);
// "super" Frame (source object) fires WindowEvent.
// "super" Frame adds "this" object as a WindowEvent listener.
setTitle("WindowEvent Demo"); // "super" Frame sets title
setSize(250, 100); // "super" Frame sets initial size
setVisible(true); // "super" Frame shows
}
// The entry main() method
public static void main(String[] args) {
new WindowEventDemo(); // Let the construct do the job
}
/* ActionEvent handler */
@Override
public void actionPerformed(ActionEvent evt) {
++count;
tfCount.setText(count + "");
}
/* WindowEvent handlers */
// Called back upon clicking close-window button
@Override
public void windowClosing(WindowEvent evt) {
System.exit(0); // Terminate the program
}
// Not Used, BUT need to provide an empty body to compile.
@Override public void windowOpened(WindowEvent evt) { }
@Override public void windowClosed(WindowEvent evt) { }
// For Debugging
@Override public void windowIconified(WindowEvent evt) { System.out.println("Window Iconified"); }
@Override public void windowDeiconified(WindowEvent evt) { System.out.println("Window Deiconified"); }
@Override public void windowActivated(WindowEvent evt) { System.out.println("Window Activated"); }
@Override public void windowDeactivated(WindowEvent evt) { System.out.println("Window Deactivated"); }
}
|
In this example, we shall modify the earlier AWTCounter example to handle the WindowEvent. Recall that pushing the "close-window" button on the AWTCounter has no effect, as it did not handle the WindowEvent of windowClosing(). We included the WindowEvent handling codes in this example.
- We identify the
super Frame as the source object.
- The
Frame fires the WindowEvent to all its registered WindowEvent listener(s).
- We select
this object as the WindowEvent listener.
- We register
this object as the WindowEvent listener to the source Frame via method addWindowListener(this).
- The
WindowEvent listener (this class) is required to implement the WindowListener interface, which declares 7 abstract methods: windowOpened(), windowClosed(), windowClosing(), windowActivated(), windowDeactivated(), windowIconified() and windowDeiconified().
- We override the
windowClosing() handler to terminate the program using System.exit(0). We ignore the other 6 handlers, but required to provide an empty body for compilation.
The sequence diagram is as follow: