A Trio of Tips for AWT Programs
Common Cleanup Code
A menu-driven GUI-based program typically reserves a menu item, labeled Exit, to exit the program. To accomplish this task, an ActionListener is registered with the Exit menu item. Then, when the user selects Exit, the ActionListener's actionPerformed method is called. In turn, this method executes cleanup code (to close a resource such as a file or media player, to save information to a file, and so on) and calls System.exit to exit the program.
Instead of selecting the Exit menu item to exit the program, the user has the option of either selecting the Close menu item from the system menu or clicking the Close (X) button in the upper-right corner of the main Frame window. Figure 1 illustrates the system menu with its Close menu item and the Close (X) button. This assumes a Windows platform.
A GUI-based Java program, running on a Windows platform, can be closed from the main Frame window either by selecting the Close menu item from the system menu or by clicking the Close button.
When either Close is selected or the Close button (X) is clicked, the AWT responds by calling the windowClosing method in the Frame's registered WindowListener. This alternate closing mechanism means that cleanup code must also be placed in windowClosing. Talk about duplication! This situation can be improved by creating a method that contains this cleanup code and by calling that method from both actionPerformed and windowClosing. Although this technique would eliminate duplicate cleanup code, method calls to the cleanup method would still be duplicated. If this cleanup method ever needs to be renamed, the name would have to be changed in at least three places—where the cleanup method is declared, actionPerformed, and windowClosing. This is unacceptable. After all, many developers like to simplify their lives. Fortunately, there is a better way.
Frame inherits the dispose method from its Window superclass. When called, dispose destroys all native resources (including memory) associated with a component object. Furthermore, a call to dispose results in a call to a registered WindowListener's windowClosed method. If a dispose method call is placed in both actionPerformed and windowClosing, the windowClosed method will be called. As a result, common cleanup code, placed in windowClosed, is guaranteed to execute, no matter how the program is closed—apart from pressing Ctrl+C under Windows or the equivalent keystroke under another platform. To demonstrate the dispose concept, Listing 1 presents source code to a SomeClass skeleton application. You must complete this skeleton by adding your own GUI.
Listing 1 The SomeClass skeleton application source code
// SomeClass.java import java.awt.*; import java.awt.event.*; class SomeClass extends Frame implements ActionListener { SomeClass () { addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent e) { // User selected close from System menu. // Call dispose to invoke windowClosed. dispose (); } public void windowClosed (WindowEvent e) { // Perform common cleanup. // Exit the program. System.exit (0); } }); // Create a menu and finish the GUI. } public void actionPerformed (ActionEvent e) { if (e.getActionCommand ().equals ("Exit")) { // Call dispose to invoke windowClosed. dispose (); return; } // Future menu items would be handled here. } public static void main (String [] args) { new SomeClass (); } }
SomeClass registers a WindowListener by calling addWindowListener with an argument consisting of a reference to an object created from an anonymous subclass of WindowAdapter. The windowClosing and windowClosed methods are overridden in this subclass.
Although SomeClass demonstrates the dispose technique for placing common cleanup code in the windowClosed method, you still need to create a GUI to see this program in action. As a result, I've created a more useful ToDoList application that demonstrates dispose and creates a GUI. Listing 2 presents ToDoList's source code.
Listing 2 The ToDoList application source code
// ToDoList.java import java.awt.*; import java.awt.event.*; import java.io.*; class ToDoList extends Frame implements ActionListener { TextArea ta; ToDoList (String title) { // Call the Frame superclass constructor to set the titlebar's // title. super (title); addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent e) { // User selected close from System menu. // Call dispose to invoke windowClosed. dispose (); } public void windowClosed (WindowEvent e) { // Save the todo list. saveList (); // Exit the program. System.exit (0); } }); // Create a TextArea component for entering and displaying // the todo list. ta = new TextArea (); // Add the TextArea component to the Frame container. add (ta); // Create a File Menu. Menu m = new Menu ("File"); // Create an Exit MenuItem. MenuItem mi = new MenuItem ("Exit"); // Add an ActionListener to the Exit MenuItem so we can exit // the program. mi.addActionListener (this); // Add the Exit MenuItem to the File Menu. m.add (mi); // Create a MenuBar. MenuBar mb = new MenuBar (); // Add the File Menu to the MenuBar. mb.add (m); // Establish the MenuBar as the Frame's MenuBar. setMenuBar (mb); // Set the Frame's window size to 200 x 200 pixels. setSize (200, 200); // Show the Frame window. setVisible (true); // Load the todo list. loadList (); // Position the insertion caret to the end of the todo list. ta.setCaretPosition (ta.getText ().length ()); } public void actionPerformed (ActionEvent e) { if (e.getActionCommand ().equals ("Exit")) { // Call dispose to invoke windowClosed. dispose (); return; } // Future menu items would be handled here. } void loadList () { FileInputStream fis = null; try { // Attempt to open todo.txt for input and associate it // with a FileInputStream. fis = new FileInputStream ("todo.txt"); // Chain a DataInputStream to the FileInputStream for // reading the file's contents as a String. DataInputStream dis = new DataInputStream (fis); // Read the file's contents as a String and set the // contents of the TextArea to the contents of the // String. ta.setText (dis.readUTF ()); } catch (IOException e) { } finally { // If todo.txt was successfully opened, close this file. if (fis != null) { try { fis.close (); } catch (IOException e) {} } } } void saveList () { FileOutputStream fos = null; try { // Attempt to open todo.txt for output and associate it // with a FileOutputStream. fos = new FileOutputStream ("todo.txt"); // Chain a DataOutputStream to the FileOutputStream for // writing the TextArea's contents to the file as a // String. DataOutputStream dos = new DataOutputStream (fos); // Read the TextArea's contents as a String and write // these contents to the file. dos.writeUTF (ta.getText ()); } catch (IOException e) { } finally { // If todo.txt was successfully opened, close this file. if (fos != null) { try { fos.close (); } catch (IOException e) {} } } } public static void main (String [] args) { // Create a ToDoList object by allocating memory and calling // ToDoList's constructor. new ToDoList ("ToDo List"); // At this point, background AWT threads have been created // (behind the scenes) which will keep this program running. // Therefore, the main thread can exit. } }
When run, ToDoList attempts to load the contents of todo.txt into its TextArea component. Changes can then be made to this text. When this program is exited by selecting Exit from the File menu, by selecting Close from the system menu, or by pressing the Close (X) button), the TextArea's contents are saved to todo.txt—thanks to the common cleanup code in windowClosed.