Now that you are armed with some knowledge of component layout managers and event handling, let's return to solving the original problem. The AWT-based solution to the problem posed by Figure 4-1 is shown in the WindowedApp program beginning below.

/**
 * This class implements a windowed app using the
 * Abstract Windowing Toolkit.
 */
import java.awt.*;
import java.io.*;
public class WindowedApp extends Frame
{
 // topEdit - the edit field for the file name
 TextField topEdit = new TextField();
 // edit - the multiline text area
 TextArea edit = new TextArea();
 // we'll need two buttons
 Button okButton = new Button("OK"); Button cancelButton= new Button("Cancel");
 // panels are invisible containers that you can place in
 // a frame to achieve (more or less) the grouping effect
 // of the text fields and buttons that you desire
 Panel topPanel = new Panel(new BorderLayout());
 Panel centerPanel = new Panel(new BorderLayout());
 Panel bottomPanel = new Panel(new BorderLayout());
 Panel bottomLeftPanel = new Panel(new BorderLayout());
 Panel bottomRightPanel = new Panel(new BorderLayout());
 /**
 * The main entry point for the app. *
 * @param args Array of parameters passed to the app
 * via the command line.
 */
 public static void main (String[] args)
 {
 // create a WindowedApp and then call its init()
 // method; this makes the app solution as much
 // like an applet as possible
 (new WindowedApp(args)).init();
 }
 /**
 * Constructor - if arguments are passed to the program,
 * assume that the first argument is the name of a file
 * to load into the edit window.
 */
 public WindowedApp(String[] args)
 {
 // if there's an argument, this is an input file
 if (args.length == 1)
 {
 try
 {
 // open an input file with the argument provided
 FileReader fr = new FileReader(args[0]);
 // now read the contents of the file and store
 // it into the edit buffer…
 int input;
 String s = new String();
 while ((input = fr.read()) != -1)
 {
 char c = (char)input;
 s = s + c;
 }
 s = convertFrom(s);
 edit.setText(s);
 // and store the name into the file name text line
 topEdit.setText(args[0]);
 }
 // if there's an error, just report it and give up any
 // hope of opening the file
 catch(Exception e)
 {
 System.out.println("Can't open input file");
 }
 }
 } /**
 * Set up the WindowedApp window.
 */
 public void init()
 {
 // start by resizing the frame and setting its title
 // (leave the frame resizable)
 setSize(300, 150);
 setTitle("AWT app");
 // first, handle the file name text field -
 // put the label on the left, a small space on the
 // right, and the text field in the middle
 topPanel.add("West", new Label("File Name:"));
 topPanel.add("Center", topEdit);
 topPanel.add("East", new Label(""));
 // create two buttons at the bottom;
 // put each in its own panel with a
 // little space on each side bottomLeftPanel.add("West", new Label(""));
 bottomLeftPanel.add("East", new Label(""));
 bottomLeftPanel.add("Center", okButton);
 bottomRightPanel.add("West", new Label(""));
 bottomRightPanel.add("East", new Label(""));
 bottomRightPanel.add("Center", cancelButton);
 // now place the two button panels in the bottom
 // panel - use a grid layout, because this divides
 // the available space up evenly between the two
 // buttons (one row, two columns)
 bottomPanel.setLayout(new GridLayout(1, 2));
 bottomPanel.add("West", bottomLeftPanel);
 bottomPanel.add("East", bottomRightPanel);
 // put the text entry field in the center panel -
 // put the label at the top and let the text area
 // take up the rest of the space
 centerPanel.add("North", new Label("Edit:"));
 centerPanel.add("Center", edit);
 // establish a border layout manager for the frame
 // and then add the three panels
 setLayout(new BorderLayout());
 add("North", topPanel);
 add("South", bottomPanel);
 add("Center", centerPanel);
 // put a little space on either side of the edit area
 add("West", new Label(""));
 add("East", new Label(""));
 // finally, now that we're all ready,
 // show the frame (frames are created hidden)
 setVisible(true);
 }
 /**
 * Process the OK button.
 */
 void processOK()
 {
 // if there's a file name in the text field, // write the contents of the edit field to that file
 String sFileName = topEdit.getText();
 if (sFileName != null)
 {
 try
 {
 // read the text field
 String s = edit.getText();
 s = convertTo(s);
 // write the contents of the text field
 // to a file using the file writer
 FileWriter fw = new FileWriter(sFileName);
 fw.write(s);
 fw.close();
 // verify that all went well
 System.out.println("File " + sFileName + " saved");
 }
 catch(Exception e)
 {
 // notify error message
 System.out.println("Output error: " + e.getMessage());
 }
 }
 // exit program
 processCancel();
 }
 /**
 * Convert output for display on MS-DOS terminal by adding
 * carriage returns after every newline character.
 */
 String convertTo(String s)
 {
 int offset = 0;
 while ((offset = s.indexOf("\n", offset)) != -1)
 {
 StringBuffer sb = new StringBuffer(s);
 sb.insert(++offset, '\r');
 s = sb.toString();
 }
 return s;
 }
 /**
 * Convert input by stripping carriage returns.
 */
 String convertFrom(String s)
 {
 int offset = 0;
 String cr = "\r";
 while((offset = s.indexOf(cr, offset)) != -1)
 {
 String s1 = s.substring(0, offset);
 String s2 = s.substring(offset + 1, s.length());
 s = s1 + s2;
 }
 return s;
 }
 /**
 * Process Cancel button by terminating program.
 */
 void processCancel()
 {
 System.out.println("Program terminating");
 System.exit(0);
 }
 /**
 * Events are passed to the window to inform it of every
 * action that occurs to the window. We are interested in
 * the following events:
 * WINDOW_DESTROY - occurs when user clicks the x window dressing
 * OK button - save file and exit
 * Cancel button - just exit
 */
 public boolean handleEvent(Event e)
 {
 // if the event target is a button…
 if (e.target instanceof Button)
 {
 // check for OK button…
 if (e.target == okButton)
 {
 processOK();
 return true; // return true to indicate that
 // we've handled the event
 }
 // now check for Cancel button
 if (e.target == cancelButton)
 {
 processCancel();
 return true; // control will never get here
 }
 }
 // if this is a Window destroy event…
 if (e.id == Event.WINDOW_DESTROY)
 {
 // then process it like a Cancel
 processCancel();
 return true; // control will never reach here
 }
 // OK, we ue; // control will never reach here
 }
 // OK, we don't know what it is - // pass it on to the base class for default processing
 return super.handleEvent(e);
 }
}

As always, execution begins with the main() method. This method creates a WindowedApp object and then uses this object to call init().

NOTE
This construct of creating an object by calling new and then immediately using the object to invoke a method is a direct app of the C++ philosophy that every expression has a type and a value (even though this construct isn't actually supported in C++).

Again please note that the statements in the init() method could have been placed directly in the constructor; however, the type of work done by init() must be separated from the constructor in applets, so Java programmers routinely separate it in apps as well.

The initialization performs the following steps:

  1. It invokes the constructor of the super class. In this case, Frame.Frame() builds the base frame object.
  2. It constructs each of the member objects. This includes topEdit, edit, okButton, cancelButton, and a number of Panel objects. Each of the panels is created with BorderLayout as its layout manager. (The default for a Panel object is the FlowLayout layout manager.)
  3. It executes the code contained in the constructor. In this case, the constructor code checks to see if a file name was provided as an argument to the app. If it was, the constructor opens that file using a FileReader object.
  4. It reads the contents of the file one character at a time until the end-of-file character is reached. Each character is returned as an int and so must be converted into a character before being added to the string s.
  5. Once input is complete, the constructor stores the accumulated string s into the edit object using the setText() method.
  6. It then assigns the file name to the topEdit object.
NOTE
The file name text area is of type TextField, whereas the edit area is of type TextArea. A TextField type is designed to accommodate a single line of text, whereas the considerably more sophisticated TextArea type can handle multiple lines of text.

The init() method starts by sizing the frame to some convenient size. It then sets the title of the frame. This title appears on the frame's title bar.

To understand the remainder of init(), you'll need to understand the plan of attack. This plan is shown in Figure 4-7. First the overall frame is divided into three panels. The upper panel contains the file name label and the file name edit box. Adding a null label to the right side of the panel keeps the edit box from extending all the way to the right edge of the frame.

On the bottom are the two buttons OK and Cancel. These are each placed in their own panel so that they can be surrounded by null labels, to give a little separation between the buttons and their surroundings. The button panels are attached to a common bottomPanel object using a GridLayout layout manager. GridLayout will divide the available horizontal space equally between the two buttons.

Screenshot-7. The plan of attack for laying out the components for WindowedApp.

Finally, the text box is placed in the middle of the center panel with null labels on both sides for separation.

Event handling

All of the event handling in WindowedApp is performed in the handleEvent() method, rather than by processing the button input in the action() method.

The target field of the Event passed to handleEvent() references the object that first received the event. Thus, if the target is an instance of Button, this event must have originated from clicking one of the buttons.

NOTE
The instanceof keyword replaces the dynamic cast mechanism in C++; it returns true if the object on the left is an instance of the class on the right. This includes any base classes. Thus, an object that is an instance of Button is automatically an instance of Component. All objects are instances of .

If the event target is the okButton object, the program calls processOK(). If the event target is the cancelButton object, the program passes control to processCancel(). The program checks for the WINDOW_DESTROY event that would originate from clicking the close window button. The program handles the close window button exactly like the cancelButton object.

NOTE
Calling functions to handle the various events rather than handling them in place is a good idea because it keeps the handleEvent() method as simple as possible.

The processCancel() method outputs a termination message prior to calling System.exit() to terminate the app.

The processOK() method begins by getting the file name from the topEdit text area. If the file name isn't null-that is, if there is a file name-the program creates a FileWriter object to handle output. The FileWriter constructor throws an exception if anything goes wrong during the opening process. WindowedApp catches this exception, outputs an error message, and continues processing.

Once the FileWriter object has been created, the program fetches the contents of the edit area and writes it to the FileWriter object.

Converting to and from

The methods convertTo() and convertFrom() are present to fix an AWT problem with the FileReader and FileWriter objects. The FileWriter object converts the String passed to it into a series of ANSI characters by ignoring the upper byte of each character. This would be fine for a UNIX machine; however, for a Windows machine, one further conversion is necessary. The newline character ("\n") must also be converted into a newline-carriage return ("\n\r").

The convertTo() method handles this conversion by searching recursively for newline characters using the String.indexOf() method. When the indexOf() method returns an offset of -1, the function knows that the conversion is complete. Until then, convertTo() inserts a carriage return at the offset following the offset that was returned. The next search begins with the character following the carriage return that was just inserted. The convertTo() method returns the resulting string.

The convertFrom() method takes the opposite tack, using a loop to search for carriage returns. Every time a carriage return is found, convertFrom() breaks the string into two strings, the first containing the characters before the carriage return and the second containing the characters after the carriage return. The program then concatenates the two strings. This process is repeated in a loop until the method returns the resulting string with all carriage returns removed.

Result

The output from the AWT-based WindowedApp is shown in Figures 4-8 through 4-10. To get a feel for the output's dynamic characteristics, I have shown it in various sizes.

Screenshot

Screenshot-8. The long awaited output from WindowedApp in its default size.

Screenshot

Screenshot-9. The output from WindowedApp expanded to roughly twice its original size.

Screenshot

Screenshot-10. The output from WindowedApp shrunk to about half its size.

You can see that as the window expands the text box components and buttons expand and reposition to retain their proportionality. This is a direct result of the BorderLayout layout manager, which ties the objects to the frame borders. We carefully placed the labels within the layout scheme so that they retain their original size and position irrespective of the size of the window.

If you continue to shrink the window, the text box shrinks proportionally until there is no longer enough room to display all of the text. At this point, the TextArea object displays scroll bars to allow access to all of the text, as shown in Figure 4-10.

NOTE
While it might be tempting to use the TextArea object for displaying large amounts of text, this won't work. The TextArea object size is limited in the size of String that it can accept. To build an editor capable of handling large files, you will need to perform dynamic file manipulations that are beyond the scope of this tutorial.