Java Programming Tutorial
Programming Graphical User Interface (GUI)
So far, we have covered the basic programming constructs (such as variables, 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 the 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 you have a good grasp of OOP, including inheritance and polymorphism; otherwise, read the earlier articles. I will describe another important concept called nested class (or inner class) in this article.
There are two sets of Java APIs for graphics programming: AWT ( A bstract W indowing T oolkit) and Swing.
- 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.
Other than AWT/Swing Graphics APIs provided in JDK, others 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 API specification (http://docs.oracle.com/javase/8/docs/api/index.html) for the AWT and Swing APIs 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.
Programming GUI with AWT
I shall start with the AWT before moving into Swing to give you a complete picture of Java Graphics.
AWT is huge! It consists of 12 packages of 370 classes (Swing is even bigger, with 18 packages of 737 classes as of JDK 1.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 , 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, and Unixes.
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 Panel s. 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 Panel s. There are five components: a Label (providing description), a TextField (for users to enter text), and three Button s (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,
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.
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 the GUI application, which 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:
- 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. Applet will be discussed in the later chapter.
Secondary Containers: Panel and ScrollPane
Secondary containers are placed inside a top-level container or another secondary container. AWT also provide these secondary containers:
- Panel : a rectangular box under a higher-level container, 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.
Hierarchy of the AWT Container Classes
The hierarchy of the AWT Container classes is as follows:
As illustrated, each Container has a layout.
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) or provide a text description.
Check the JDK API specification for java.awt.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)
These three constants are defined for specifying the alignment of the Label ‘s text, as used in the above constructor.
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.
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.
AWT GUI Component: java.awt.Button
A java.awt.Button is a GUI component that triggers a certain programmed action upon clicking.
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.
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.
Clicking a button fires a so-called ActionEvent and triggers a certain programmed action. I will explain event-handling 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 .
Hitting the "ENTER" key on a TextField fires a ActionEvent , and triggers a certain programmed action.
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 .
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.
To exit this program, you have to close the CMD-shell (or press "control-c" on the CMD console); or push the "red-square" 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() :
The output (with my comments) are as follows. You could have an insight of the variables defined in the class.
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 .
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.TextField s. 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:
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 and Delphi).
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). This is unlike the procedural model, where codes are executed in a sequential manner.
The AWT’s event-handling classes are kept in package java.awt.event .
Three 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, it creates an event object. 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 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.
Source object fires event event upon triggered. For example, clicking an Button fires an ActionEvent , mouse-click fires MouseEvent , key-type fires KeyEvent , 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,
Secondly, all the listeners interested in the XxxEvent 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 listenser(s) can response to these events appropriately. For example,
Thirdly, in the source, we need to maintain a list of listener object(s), and define two methods: addXxxListener() and removeXxxListener() to add and remove a listener from this list. The signature of the methods are:
Take note that all the listener(s) interested in the XxxEvent must implement the XxxListener interface. That is, they are sub-type of the XxxListener . Hence, they can be upcasted to XxxListener and passed as the argument of the above methods.
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() method:
In summary, triggering a source fires an event to all its registered listeners, and invoke an appropriate handler of the listener.
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 actionPerformed() as follow:
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() .
- The source object registers listener via the addActionListener() . In this example, the source btnCount ( Button ) adds " this " object as a listener via:
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.
The sequence diagram is as follows:
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.
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.
The following program added support for "close-window button" to Example 1: AWTCounter .
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:
Example 4: MouseEvent and MouseListener Interface
A MouseEvent is fired when you press, release, or click (press followed by release) a mouse-button (left or right button) at the source object; or position the mouse-pointer at (enter) and away (exit) from the source object.
A MouseEvent listener must implement the MouseListener interface, which declares the following five abstract methods:
In this example, we setup a GUI with 4 components (two Label s and two non-editable TextField s) inside a top-level container Frame , arranged in FlowLayout .
To demonstrate the MouseEvent :
- We identity super Frame as the source object.
- The Frame fires a MouseEvent to all its MouseEvent listener(s) when you click/press/release a mouse-button or enter/exit with the mouse-pointer.
- We select this object as the MouseEvent listener.
- We register this object as the MouseEvent listener to super Frame (source) via the method addMouseListener(this) .
- The listener ( this class) is required to implement the MouseListener interface, which declares 5 abstract methods: mouseClicked() , mousePressed() , mouseReleased() , mouseEntered() , and mouseExit() . We override the mouseClicked() to display the (x, y) co-ordinates of the mouse click on the two displayed TextField s. We ignore all the other handlers (for simplicity – but you need to provide an empty body for compilation).
Try: Include a WindowListener to handle the close-window button.
Example 5: MouseEvent and MouseMotionListener Interface
A MouseEvent is also fired when you move and drag the mouse pointer at the source object. But you need to use MouseMotionListener to handle the mouse-move and mouse-drag. The MouseMotionListener interface declares the following two abstract methods:
In this example, we shall illustrate both the MouseListener and MouseMotionListener .
- We identify the super Frame as the source, which fires the MouseEvent to its registered MouseListener and MouseMotionListener .
- We select this object as the MouseListener and MouseMotionListner .
- We register this object as the listener to super Frame via method addMouseListener(this) and addMouseMotionListener(this) .
- The MouseMotionListener ( this class) needs to implement 2 abstract methods: mouseMoved() and mouseDragged() declared in the MouseMotionListener interface.
- We override the mouseMoved() to display the (x, y) position of the mouse pointer. We ignore the MouseDragged() handler by providing an empty body for compilation.
Try: Include a WindowListener to handle the close-window button.
Example 6: KeyEvent and KeyListener Interface
A KeyEvent is fired when you pressed, released, and typed (pressed followed by released) a key on the source object. A KeyEvent listener must implement KeyListener interface, which declares three abstract methods:
In this example:
- We identify the tfInput ( TextField ) as the source object.
- The source fires a KeyEvent when you press/release/type a key to all its KeyEvent listener(s).
- We select this object as the KeyEvent listener.
- We register this object as the KeyEvent listener to the source TextField via method input.addKeyListener(this) .
- The KeyEvent listener ( this class) needs to implement the KeyListener interface, which declares 3 abstract methods: keyTyped() , keyPressed() , keyReleased() .
- We override the keyTyped() to display key typed on the display TextArea . We ignore the keyPressed() and keyReleased() .
Nested (Inner) Classes
A nested class (or commonly called inner class) is a class defined inside another class – introduced in JDK 1.1. As an illustration, two nested classes MyNestedClass1 and MyNestedClass2 are defined inside the definition of an outer class called MyOuterClass .
A nested class has these properties:
- A nested class is a proper class. That is, it could contain constructors, member variables and member methods. You can create an instance of a nested class via the new operator and constructor.
- A nested class is a member of the outer class, just like any member variables and methods defined inside a class.
- Most importantly, a nested class can access the private members (variables/methods) of the enclosing outer class, as it is at the same level as these private members. This is the property that makes inner class useful.
- A nested class can have private , public , protected , or the default access, just like any member variables and methods defined inside a class. A private inner class is only accessible by the enclosing outer class, and is not accessible by any other classes. [An top-level outer class cannot be declared private , as no one can use a private outer class.]
- A nested class can also be declared static , final or abstract , just like any ordinary class.
- A nested class is NOT a subclass of the outer class. That is, the nested class does not inherit the variables and methods of the outer class. It is an ordinary self-contained class. [Nonetheless, you could declare it as a subclass of the outer class, via keyword " extends OuterClassName ", in the nested class’s definition.]
The usages of nested class are:
- To control visibilities (of the member variables and methods) between inner/outer class. The nested class, being defined inside an outer class, can access private members of the outer class.
- To place a piece of class definition codes closer to where it is going to be used, to make the program clearer and easier to understand.
- For namespace management.
Example 7: A Named Inner Class as Event Listener
A nested class is useful if you need a small class which relies on the enclosing outer class for its private variables and methods. It is ideal in an event-driven environment for implementing event handlers. This is because the event handling methods (in a listener) often require access to the private variables (e.g., a private TextField ) of the outer class.
In this example (modified from Example 1 AWTCounter ), instead of using " this " as the ActionEvent listener for the Button , we define a new class called BtnCountListener , and create an instance of BtnCountListener as the ActionEvent listener for the btnCount . The BtnCountListener needs to implement the ActionListener interface, and override the actionPerformed() handler. Since " this " is no long a ActionListener , we remove the " implements ActionListener " from " this " class’s definition.
BtnCountListener needs to be defined as an inner class, as it needs to access private variables ( count and tfCount ) of the outer class.
Dissecting the Program
- An inner class named BtnCountListener is used as the ActionListner .
- An anonymous instance of the BtnCountListener inner class is constructed. The btnCount source object adds this instance as a listener, as follows:
- The inner class can access the private variable tfCount and count of the outer class.
- Since " this " is no longer a listener, we remove the " implements ActionListener " from this class’ definition.
- The inner class is compiled into AWTCount$BtnCountListener.class , in the format of OuterClassName$InnerClassName.class .
(Advanced) Using an Ordinary (Outer) Class as Listener
Try moving the BtnCountListener class outside, and define it as an ordinary class. You would need to pass a reference of the AWTConnter into the constructor of BtnCountListener , and use this reference to access variables tfCount and count , through public getters or granting them to public access.
This code is messy! Inner class provides a much cleaner solution!
Example 8: An Anonymous Inner Class as Event Listener
Instead of using a named inner class (called BtnCountListner in the previous example), we shall use an inner class without a name, known as anonymous inner class as the ActionListener in this example.
Dissecting the Program
- Again, " this " class is NOT used as the ActionEvent listener. Hence, we remove the " implements ActionListener " from this class’ definition.
- The anonymous inner class is given a name generated by the compiler, and compiled into OuterClassName$n.class , where n is a running number of the inner classes of this outer class.
- An anonymous instance of an anonymous inner class is constructed, and passed as the argument of the addActionListener() method as follows:
The above codes is equivalent to and compiled as:
Properties of Anonymous Inner Class
- The anonymous inner class is define inside a method, instead of a member of the outer class (class member). It is local to the method and cannot be marked with access modifier (such as public , private ) or static , just like any local variable of a method.
- An anonymous inner class must always extend a superclass or implement an interface. The keyword " extends " or " implements " is NOT required in its declaration. An anonymous inner class must implement all the abstract methods in the superclass or in the interface.
- An anonymous inner class always uses the default (no-arg) constructor from its superclass to create an instance. If an anonymous inner class implements an interface, it uses the java.lang.Object() .
- An anonymous inner class is compiled into a class named OuterClassName$n.class , where n is a running number of inner classes within the outer class.
- An instance of an anonymous inner class is constructed via this syntax:
The created instance can be assigned to a variable or used as an argument of a method.
Example 9: An Anonymous Inner Class for Each Source
Let’s modify our AWTCounter example to include 3 buttons for counting up, counting down, and reset the count, respectively. We shall attach an anonymous inner class as the listener to each of buttons.
Dissecting the Program
- Each of the Button s uses one anonymous instance of an anonymous inner class as its ActionEvent listener.
Example 10: Using the Same Listener Instance for All the Buttons
If you use the same instance as the listener for all the 3 buttons, you need to determine which button has fired the event. It is because all the 3 buttons trigger the same event-handler method.
Using ActionEvent ‘s getActionCommand()
In the following example, we use the same instance of a "named" inner class as the listener for all the 3 buttons. The listener needs to determine which button has fired the event. This can be accomplished via the ActionEvent ‘s getActionCommonad() method, which returns the button’s label.
Using getSource() of EventObject
Besides the getActionCommand() , which is only available for ActionEvent , you can use the getSource() method, which is available to all event objects, to retrieve a reference to the source object that has fired the event. getSource() returns a java.lang.Object . You may need to downcast it to the proper type of the source object. For example,
Event Listener’s Adapter Classes
Example 11: WindowAdapter for WindowListener
Using WindowListener Interface
Refer to the WindowEventDemo , a WindowEvent listener is required to implement the WindowListener interface, which declares 7 abstract methods. Although we are only interested in windowClosing() , we need to provide an empty body to the other 6 abstract methods in order to compile the program. This is tedious, e.g., we can rewrite the WindowEventDemo using an inner class implementing ActionListener as follows:
Using WindowAdapter Superclass
An adapter class called WindowAdapter is therefore provided, which implements the WindowListener interface and provides default implementations to all the 7 abstract methods. You can then derive a subclass from WindowAdapter and override only methods of interest and leave the rest to their default implementation. For example,
Clearly, the adapter greatly simplifies the codes.
Other Event-Listener Adapter Classes
Similarly, adapter classes such as MouseAdapter , MouseMotionAdapter , KeyAdapter , FocusAdapter are available for MouseListener , MouseMotionListener , KeyListener , and FocusListener , respectively.
There is no ActionAdapter for ActionListener , because there is only one abstract method (i.e. actionPerformed() ) declared in the ActionListener interface. This method has to be overridden and there is no need for an adapter.
Layout Managers and Panel
A container has a so-called layout manager to arrange its components. The layout managers provide a level of abstraction to map your user interface on all windowing systems, so that the layout can be platform-independent.
AWT provides the following layout managers (in package java.awt ): FlowLayout , GridLayout , BorderLayout , GridBagLayout , BoxLayout , CardLayout , and others. Swing added more layout manager in package javax.swing , to be described later.
Container’s setLayout() method
A container has a setLayout() method to set its layout manager:
To set up the layout of a Container (such as Frame , JFrame , Panel , or JPanel ), you have to:
- Construct an instance of the chosen layout object, via new and constructor, e.g., new FlowLayout() )
- Invoke the setLayout() method of the Container , with the layout object created as the argument;
- Place the GUI components into the Container using the add() method in the correct order; or into the correct zones.
Container’s getLayout() method
You can get the current layout via Container ‘s getLayout() method.
Panel’s Initial Layout
Panel (and Swing’s JPanel ) provides a constructor to set its initial layout manager. It is because a primary function of Panel is to layout a group of component in a particular layout.
In the java.awt.FlowLayout , components are arranged from left-to-right inside the container in the order that they are added (via method aContainer.add(aComponent) ). When one row is filled, a new row will be started. The actual appearance depends on the width of the display window.
In java.awt.GridLayout , components are arranged in a grid (matrix) of rows and columns inside the Container . Components are added in a left-to-right, top-to-bottom manner in the order they are added (via method aContainer.add(aComponent) ).
In java.awt.BorderLayout , the container is divided into 5 zones: EAST , WEST , SOUTH , NORTH , and CENTER . Components are added using method aContainer.add(acomponent, zone) , where zone is either BorderLayout.NORTH (or PAGE_START ), BorderLayout.SOUTH (or PAGE_END ), BorderLayout.WEST (or LINE_START ), BorderLayout.EAST (or LINE_END ), or BorderLayout.CENTER .
You need not place components to all the 5 zones. The NORTH and SOUTH components may be stretched horizontally; the EAST and WEST components may be stretched vertically; the CENTER component may stretch both horizontally and vertically to fill any space left over.
Using Panel s as Sub-Container to Organize Components
An AWT Panel is a rectangular pane, which can be used as sub-container to organized a group of related components in a specific layout (e.g., FlowLayout , BorderLayout ). Panel s are secondary containers, which shall be added into a top-level container (such as Frame ), or another Panel .
For example, the following figure shows a Frame in BorderLayout containing two Panels – panelResult in FlowLayout and panelButtons in GridLayout . panelResult is added to the NORTH , and panelButtons is added to the CENTER .
BoxLayout arrange components in a single row or column. It respects components’ requests on the minimum sizes.
[TODO] Example and diagram
Swing is part of the so-called "Java Foundation Classes (JFC)" (have you heard of MFC?), which was introduced in 1997 after the release of JDK 1.1. JFC was subsequently included as an integral part of JDK since JDK 1.2. JFC consists of:
- Swing API: for advanced graphical programming.
- Accessibility API: provides assistive technology for the disabled.
- Java 2D API: for high quality 2D graphics and images.
- Pluggable look and feel supports.
- Drag-and-drop support between Java and native applications.
The goal of Java GUI programming is to allow the programmer to build GUI that looks good on ALL platforms. JDK 1.0’s AWT was awkward and non-object-oriented (using many event.getSource() ). JDK 1.1’s AWT introduced event-delegation (event-driven) model, much clearer and object-oriented. JDK 1.1 also introduced inner class and JavaBeans вЂ“ a component programming model for visual programming environment (similar to Visual Basic and Dephi).
Swing appeared after JDK 1.1. It was introduced into JDK 1.1 as part of an add-on JFC (Java Foundation Classes). Swing is a rich set of easy-to-use, easy-to-understand JavaBean GUI components that can be dragged and dropped as "GUI builders" in visual programming environment. Swing is now an integral part of Java since JDK 1.2.
Swing is huge (consists of 18 packages of 737 classes as in JDK 1.8) and has great depth. Compared with AWT, Swing provides a huge and comprehensive collection of reusable GUI components, as shown in the Figure below (extracted form Swing Tutorial).
The main features of Swing are (extracted from the Swing website):
- Swing is written in pure Java (except a few classes) and therefore is 100% portable.
- Swing components are lightweight. The AWT components are heavyweight (in terms of system resource utilization). Each AWT component has its own opaque native display, and always displays on top of the lightweight components. AWT components rely heavily on the underlying windowing subsystem of the native operating system. For example, an AWT button ties to an actual button in the underlying native windowing subsystem, and relies on the native windowing subsystem for their rendering and processing. Swing components ( JComponent s) are written in Java. They are generally not "weight-down" by complex GUI considerations imposed by the underlying windowing subsystem.
- Swing components support pluggable look-and-feel. You can choose between Java look-and-feel and the look-and-feel of the underlying OS (e.g., Windows, UNIX or Mac). If the later is chosen, a Swing button runs on the Windows looks like a Windows’ button and feels like a Window’s button. Similarly, a Swing button runs on the UNIX looks like a UNIX’s button and feels like a UNIX’s button.
Using Swing API
If you understood the AWT programming (in particular, container/component and event-handling), switching over to Swing (or any other Graphics packages) is straight-forward.
Compared with the AWT component classes (in package java.awt ), Swing component classes (in package javax.swing ) begin with a prefix "J" , e.g., JButton , JTextField , JLabel , JPanel , JFrame , or JApplet .
The above figure shows the class hierarchy of the swing GUI classes. Similar to AWT, there are two groups of classes: containers and components. A container is used to hold components. A container can also hold containers because it is a (subclass of) component.
As a rule, do not mix heavyweight AWT components and lightweight Swing components in the same program, as the heavyweight components will always be painted on top of the lightweight components.
Swing’s Top-Level and Secondary Containers
Just like AWT application, a Swing application requires a top-level container. There are three top-level containers in Swing:
- JFrame : used for the application’s main window (with an icon, a title, minimize/maximize/close buttons, an optional menu-bar, and a content-pane), as illustrated.
- JDialog : used for secondary pop-up window (with a title, a close button, and a content-pane).
- JApplet : used for the applet’s display-area (content-pane) inside a browserвЂ™s window.
Similarly to AWT, there are secondary containers (such as JPanel ) which can be used to group and layout relevant components.
The Content-Pane of Swing’s Top-Level Container
However, unlike AWT, the JComponents shall not be added onto the top-level container (e.g., JFrame , JApplet ) directly because they are lightweight components. The JComponents must be added onto the so-called content-pane of the top-level container. Content-pane is in fact a java.awt.Container that can be used to group and layout components.
- get the content-pane via getContentPane() from a top-level container, and add components onto it. For example,
- set the content-pane to a JPanel (the main panel created in your application which holds all your GUI components) via JFrame ‘s setContentPane() .
Notes: If a component is added directly into a JFrame , it is added into the content-pane of JFrame instead, i.e.,
Event-Handling in Swing
Swing uses the AWT event-handling classes (in package java.awt.event ). Swing introduces a few new event-handling classes (in package javax.swing.event ) but they are not frequently used.
Writing Swing Applications
In summary, to write a Swing application, you have:
- Use the Swing components with prefix "J" in package javax.swing , e.g., JFrame , JButton , JTextField , JLabel , etc.
- A top-level container (typically JFrame ) is needed. The JComponents should not be added directly onto the top-level container. They shall be added onto the content-pane of the top-level container. You can retrieve a reference to the content-pane by invoking method getContentPane() from the top-level container.
- Swing applications uses AWT event-handling classes, e.g., ActionEvent/ActionListener , MouseEvent/MouseListener , etc.
- Run the constructor in the Event Dispatcher Thread (instead of Main thread) for thread safety, as shown in the following program template.
Swing Program Template
I will explain this template in the following Swing example.
Swing Example 1: SwingCounter
Let’s convert the earlier AWT application example into Swing. Compare the two source files and note the changes (which are highlighted). The display is shown below. Note the differences in look and feel between the AWT GUI components and Swing’s.
JFrame ‘s Content-Pane
The JFrams ‘s method getContentPane() returns the content-pane (which is a java.awt.Containter ) of the JFrame . You can then set its layout (the default layout is BorderLayout ), and add components into it. For example,
You can also use the JFrame ‘s setContentPane() method to directly set the content-pane to a JPanel (or a JComponent ). For example,
Instead of writing a WindowEvent listener with a windowClosing() handler to process the "close-window" button, JFrame provides a method called setDefaultCloseOperation() to sets the default operation when the user initiates a "close" on this frame. Typically, we choose the option JFrame.EXIT_ON_CLOSE , which terminates the application via a System.exit() .
Running the GUI Construction Codes on the Event-Dispatching Thread
In the previous examples, we invoke the constructor directly in the entry main() method to setup the GUI components. For example,
The constructor will be executed in the so-called "Main-Program" thread. This may cause multi-threading issues (such as unresponsive user-interface and deadlock).
It is recommended to execute the GUI setup codes in the so-called "Event-Dispatching" thread, instead of "Main-Program" thread, for thread-safe operations. Event-dispatching thread, which processes events, should be used when the codes updates the GUI.
To run the constructor on the event-dispatching thread, invoke static method SwingUtilities.invokeLater() to asynchronously queue the constructor on the event-dispatching thread. The codes will be run after all pending events have been processed. For example,
Note: javax.swing.SwingUtilities.invokeLater() is a cover for java.awt.EventQueue.invokeLater() (which is used in the NetBeans’ Visual GUI Builder).
At times, for example in game programming, the constructor or the main() may contains non-GUI codes. Hence, it is a common practice to create a dedicated method called initComponents() (used in NetBeans visual GUI builder) or createAndShowGUI() (used in Swing tutorial) to handle all the GUI codes (and another method called initGame() to handle initialization of the game’s objects). This GUI init method shall be run in the event-dispatching thread.
Warning Message "The serialization class does not declare a static final serialVersionUID field of type long"
This warning message is triggered because java.awt.Frame (via its superclass java.awt.Component ) implements the java.io.Serializable interface. This interface enables the object to be written out to an output stream serially (via method writeObject() ); and read back into the program (via method readObject() ). The serialization runtime uses a number (called serialVersionUID ) to ensure that the object read into the program is compatible with the class definition, and not belonging to another version.
You have these options:
- Simply ignore this warning message. If a serializable class does not explicitly declare a serialVersionUID , then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class.
- Add a serialVersionUID (Recommended), e.g.
- Suppress this particular warning via annotation @SuppressWarmomgs (in package java.lang ) (JDK 1.5):
Swing Example 2: SwingAccumulator
Using Visual GUI Builder – NetBeans/Eclipse
If you have a complicated layout for your GUI application, you should use a GUI Builder, such as NetBeans or Eclipse to layout your GUI components in a drag-and-drop manner, similar to the popular visual languages such as Visual Basic and Dephi.