Chapter 19

Organizing Window Programs


CONTENTS


This chapter covers the basics of writing window programs. It shows you how window programs are structured and organized and identifies the basic approach to designing most window programs. It covers the details of the Frame class and explains how windows are opened and closed. The five basic window layouts are introduced and illustrated through a sample program. The finer points of window event handling are then described. Finally, a window program is created that introduces the most common window GUI controls. When you finish this chapter, you will have had a broad introduction to window programming and will be familiar with most of the common window GUI controls.

Designing Window Programs

The design of most window programs usually involves two basic steps: laying out the program's graphical user interface and providing the functionality that implements the interface.

The first step addresses one of the most important features of window programs-its look and feel. Window programs are preferred to console programs when their look and feel are interesting, innovative, and help the user to accomplish a particular purpose.

A program's look is determined by how it presents itself to users. It consists of all those characteristics that determine its appearance, such as window size, layout, background and foreground colors, menus, and GUI controls. A program's feel is determined by the availability of easy-to-use GUI controls and the contribution of these controls to the program's ultimate intended use. It is the result of the designer's ability to select and implement those GUI controls that enhance a program's capability to satisfy user expectations.

The window's GUI design begins by creating an application window, using the Frame class, and determining the basic characteristics of the window such as its size, title, background and foreground colors, and general layout. Next a menu bar is added to the window, and the program's menus and menu items are added to the menu bar. The GUI controls that are to be used in the window are determined, designed, and attached to the window's panels and frame.

At this point, you know what your program will look like and you can concentrate on what it will do. The first step in bringing your program's user interface to life is to add the event-handling software required to respond to events generated as the result of user interaction. The event-handling software will not immediately implement all user actions, but it should respond to them and provide hooks for eventual implementation of all user interface actions. The event-handling software is then fleshed out to provide all the functionality required of the application program. The program's design and implementation reaches an Alpha stage when all required user-interface functions have been implemented.

The next stage of program development is to refine and test the program to make it more responsive to its intended purpose. A series of Beta versions of the program are developed that implement user feedback and fix any identified errors or deficiencies. Finally, the program is refined to handle unusual user inputs and to process errors and exceptions.

Figure 19.1 provides an overview of the process of designing and implementing window programs. This chapter covers the basics of creating and organizing window programs and shows how to connect event-handling code to general window components. A window sampler program is provided that illustrates the basic use of common window GUI controls. Subsequent chapters explore the use of these GUI controls in more detail.

Figure 19.1 : The process for window design and implementation.

Opening and Closing Windows

Opening and closing windows mark the beginning and end of any window program. The Frame class provides the basis by which these fundamental window operations are accomplished. A Frame object implements an application main window, inheriting many methods that enable it to do so from the Window, Container, and Component classes.

To open an application window, a Frame object is created and its show() method is invoked. The show() method is inherited from the Window class. To close an application window, the WINDOW_DESTROY event must be handled. The window is disposed of using the dispose() method of the Window class, or more commonly by invoking the System.exit() method after performing any necessary program-termination processing.

The Frame class and its ancestors provide a number of methods that control the way in which a window is displayed. The setBackground() and setForeground() methods inherited from the Component class are used to specify a window's background and foreground colors. The setFont() method, also inherited from Component, is used to specify the default font to be used with a window. The Frame class, itself, provides a number of methods that control a window's appearance. The setTitle() method allows a window's title to be changed. The setCursor() method allows the cursor to be changed while it is in the window's focus. The setMenuBar() method enables a menu bar to be attached to a window, and the setResizable() method toggles whether a window can or cannot be resized. The setIconImage() method allows the window's minimized icon to be changed. This method is not supported by all Java implementations and therefore should be avoided if compatibility is a concern.

The FrameApp program shown in Listing 19.1 illustrates the window concepts covered so far and shows the effect of using the basic window controls identified in the previous paragraph.


Listing 19.1. The source code of the FrameApp program.

import java.awt.*;

public class FrameApp extends Frame {
 String defaultTitle;
 MenuBar defaultMenuBar;
 MenuBar alternativeMenuBar;
 int cursors[] = {CROSSHAIR_CURSOR,DEFAULT_CURSOR,E_RESIZE_CURSOR,HAND_CURSOR,
  MOVE_CURSOR,NE_RESIZE_CURSOR,NW_RESIZE_CURSOR,N_RESIZE_CURSOR,
  SE_RESIZE_CURSOR,SW_RESIZE_CURSOR,S_RESIZE_CURSOR,TEXT_CURSOR,
  WAIT_CURSOR,W_RESIZE_CURSOR};
 Color colors[] = {Color.black,Color.blue,Color.cyan,Color.darkGray,Color.gray,
  Color.green,Color.lightGray,Color.magenta,Color.orange,Color.pink,Color.red,
  Color.white,Color.yellow};
 String fontNames[] = {"Helvetica","TimesRoman","Courier","Dialog",
  "DialogInput","ZapfDingbats"};
 int cursorIndex = 1;
 int backgroundColorIndex = 0;
 int foregroundColorIndex = 0;
 int fontIndex = 0;
 public static void main(String args[]){
  FrameApp app = new FrameApp();
 }
 public FrameApp() {
  super("Exploring Frames");
  defaultTitle = getTitle();
  setup();
  pack();
  resize(400,400);
  show();
 }
 void setup() {
  setupPanels();
  setupMenuBars();
  setFont(new Font(fontNames[fontIndex],Font.PLAIN,14));
 }
 void setupPanels() {
  Panel mainPanel = new Panel();
  mainPanel.setLayout(new GridLayout(4,1));
  Label label1 =
   new Label("Change these windows characteristics:",Label.CENTER);
  mainPanel.add(label1);
  Panel panel1 = new Panel();
  panel1.add(new Button("Title"));
  panel1.add(new Button("Menu Bar"));
  panel1.add(new Button("Resizable"));
  mainPanel.add(panel1);
  Label label2 = new Label("Check out these windows options:",Label.CENTER);
  mainPanel.add(label2);
  Panel panel2 = new Panel();
  panel2.add(new Button("Cursor"));
  panel2.add(new Button("Background"));
  panel2.add(new Button("Foreground"));
  panel2.add(new Button("Font"));
  mainPanel.add(panel2);
  add("South",mainPanel);
 }
 void setupMenuBars() {
  defaultMenuBar = new MenuBar();
  Menu fileMenu = new Menu("File");
  fileMenu.add(new MenuItem("Exit"));
  defaultMenuBar.add(fileMenu);
  setMenuBar(defaultMenuBar);
  alternativeMenuBar = new MenuBar();
  Menu otherMenu = new Menu("Program");
  otherMenu.add(new MenuItem("Quit"));
  alternativeMenuBar.add(otherMenu);
 }
 public void paint(Graphics g) {
  g.drawString("Sample Text",160,100);
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.WINDOW_DESTROY){
   System.exit(0);
   return true;
  }else if(event.id==Event.ACTION_EVENT){
   if(event.target instanceof Button){
    if("Title".equals(event.arg)){
     if(defaultTitle.equals(getTitle()))
      setTitle("Here's an alternative title.");
     else setTitle(defaultTitle);
     return true;
    }else if("Menu Bar".equals(event.arg)){
     if(defaultMenuBar.equals(getMenuBar()))
      setMenuBar(alternativeMenuBar);
     else setMenuBar(defaultMenuBar);
     return true;
    }else if("Resizable".equals(event.arg)){
     setResizable(!isResizable());
     return true;
    }else if("Cursor".equals(event.arg)){
     ++cursorIndex;
     cursorIndex %= cursors.length;
     setCursor(cursors[cursorIndex]);
     return true;
    }else if("Background".equals(event.arg)){
     ++backgroundColorIndex;
     backgroundColorIndex %= colors.length;
     setBackground(colors[backgroundColorIndex]);
     repaint();
     return true;
    }else if("Foreground".equals(event.arg)){
     ++foregroundColorIndex;
     foregroundColorIndex %= colors.length;
     setForeground(colors[foregroundColorIndex]);
     repaint();
     return true;
    }else if("Font".equals(event.arg)){
     ++fontIndex;
     fontIndex %= fontNames.length;
     setFont(new Font(fontNames[fontIndex],Font.PLAIN,14));
     repaint();
     return true;
    }
   }else if(event.target instanceof MenuItem){
    if("Exit".equals(event.arg) || "Quit".equals(event.arg)){
     System.exit(0);
     return true;
    }
   }
  }
  return false;
 }
}


After you have compiled FrameApp, run it and check out the buttons and menus it provides. When you first launch FrameApp, it displays the text Sample Text followed by two rows of buttons, as shown in Figure 19.2. The buttons in the first row provide the capability to change the window's title, menu bar, and resizable properties. These buttons are toggles-the second time you click a button, the window's characteristic being changed is reverted to its initial default value. The buttons in the second row allow you to step through a sequence of values for the window's cursor, background and foreground colors, and text font. If you are using Windows 95, you will find that it does not support all cursors defined by Java. You will find out which cursors are not supported when you analyze this program's code.

Figure 19.2 : The FrameApp initial display.

Let's investigate each of the program's features. Click on the Title button and you will notice that the window's title text is changed from Exploring Frames to Here's an alternative title., as shown in Figure 19.3. Click on the button a second time and the title changes back to Exploring Frames.

Figure 19.3 : Here's an alternative title.

Click on the File menu, but don't select the Exit option. The File menu is replaced by the Program menu, shown in Figure 19.4, when you click on the Menu Bar button. Adding, deleting, and modifying menus are common operations for many window programs.

Figure 19.4 : The Program menu.

You will notice that your program window is initially resizable. This means that you can use the cursor at the window's edge to make the window larger or smaller. The cursor changes from a pointer to a resizable icon to let you know that the window can be resized. If you click on the Resizable button, the window is no longer capable of being resized. You can check this by placing your cursor at the window's boundary.

Click on the Cursor button to change the cursor associated with your program's window. Step through the list of available cursors until you reach the crosshair cursor. Then click the cursor button one more time to return to the default cursor.

Click the Background button and the program's background color is changed to blue, as shown in Figure 19.5. You can continue to click the Background button to look at other background colors. Notice that the color does not change in the panel used by the program's buttons. Now try clicking on the Foreground button a few times to change the window's foreground color.

Figure 19.5 : Changing the window background.

The Font button allows you to change the font used with the sample text display as shown in Figure 19.6. Cycle through the list of fonts. The last font in the list is the ZapfDingbats font. It is used to display special characters and symbols, as shown in Figure 19.6.

Figure 19.6 : ZapDingbats characters.

You have covered the program's features. Now let's look at its code to see how it works.

The FrameApp class defines a number of variables. The defaultTitle variable is used to store the default window title so that it can be restored after it has been changed. The defaultMenuBar and alternativeMenuBar variables are used to store the File and Program menu bars. The cursors[] array stores the cursor constants that are defined for the Frame class. These constants are stored in an array to make it easier to cycle through them. The cursorIndex variable maintains an index to the current cursor being displayed.

The colors[] array stores a subset of the colors defined by the Color class. These colors are used when the background and foreground colors are changed. The backgroundColorIndex and foregroundColorIndex variables are used to keep track of the current background and foreground colors.

The fontNames[] array stores the names of the fonts known to Java. The fontIndex variable is used to point to the current window font.

FrameApp has a simple standard main() method that creates a new FrameApp object. The FrameApp constructor invokes the superclass constructor call statement to set the window's title and then uses the getTitle() method inherited from Frame to store the title in the defaultTitle variable. It then invokes the setup() method to set up panels, buttons, menu bars, and the default window font. The pack() method and the resize() methods are used to organize the components of the window and adjust it to the desired size. The show() method then causes the window to be opened and displayed.

The setup() method invokes the setupPanels() and setupMenuBars() methods to set up the program's panels, buttons, and menu bars. It invokes the setFont() method inherited from the Component class to set the default font to be used with the window. The setFont() method takes three arguments: the name of the font, the font style, and the size of the font in points. The font names are stored in the fontNames[] array. The font style constants are defined in the Font class.

The setupPanels() method constructs a Panel object named mainPanel that is to hold the subpanels corresponding to the rows of buttons. The layout of the mainPanel is set to a GridLayout object of four rows and one column. Layouts are used to specify how objects are to be placed in a container. They are covered in the "Using Layouts" section in this chapter.

A new Label object is created with the text Change these windows characteristics: and added to the mainPanel. The Label.CENTER constant specifies how the label should be aligned within its available space. A Panel object named panel1 is created to hold the Title, Menu Bar, and Resizable buttons that are subsequently created and added to the panel. The panel1 object is then added to the mainPanel.

A second Label object named label2 is created to hold the text Check out these windows options:. The label is then added to the mainPanel. A second Panel object named panel2 is created. The Cursor, Background, Foreground, and Font buttons are then added to panel2. The panel2 object is then added to the mainPanel. The mainPanel is then added to the southern (bottom) region of the FrameApp object being constructed. The "Using Layouts" section in this chapter describes why the FrameApp object was organized with a mainPanel and two subpanels.

The setupMenuBars() method creates a new MenuBar object and assigns it to the defaultMenuBar variable. It declares the fileMenu variable and assigns it a new Menu object with the File label. It creates a MenuItem object with the Exit label and adds it to the fileMenu. The defaultMenuBar is then set as the menu bar of the FrameApp object being constructed via the setMenuBar() method of the Frame class. Menus are covered in Chapter 20, "Menus, Buttons, and Dialog Boxes." For now, just remember that a Frame has a MenuBar that consists of one or more Menus that each have one or more MenuItems.

The alternativeMenuBar is constructed in the same manner as the defaultMenuBar except that it contains an otherMenu with the File and Exit labels replaced by Program and Quit labels.

The alternativeMenuBar is not set to the FrameApp using the setMenuBar() method. A Frame object can have only one menu bar.

The paint() method is used to initially draw the window display and then to update it as the result of window operations. It simply displays the text Sample Text at pixel location (160,100) within the window Graphics object.

The handleEvent() method is the last method to be covered. It is responsible for handling all of the user-generated events and provides most of the program's functionality. The handleEvent() method handles the WINDOWS_DESTROY event by invoking the System.exit() method to terminate the program's execution. It also handles the Button and MenuItem action events. The Exit and Quit menu items also result in the program's execution being terminated.

When the Title button is clicked, handleEvent() uses the getTitle() method to determine whether the current window title equals the title stored in defaultTitle. If they are equal, the window's title is set to Here's an alternative title. If the current title differs from the value stored in defaultTitle, the window's title is set using defaultTitle.

When the Menu Bar button is clicked, handleEvent() uses the getMenuBar() method to check whether the window's current menu bar is the same as the value of defaultMenuBar. If they are the same, the window's menu bar is set to the value of alternativeMenuBar. If they are different, the window's menu bar is set to the value of defaultMenuBar.

When the Resizable button is clicked, handleEvent() uses the isResizable() method to determine whether the window is currently resizable and then sets it to the opposite value.

When the Cursor button is clicked, handleEvent() cycles the cursorIndex to the next cursor value within the cursors[] array and sets the cursor to this value using the setCursor() method.

When the Background button is clicked, handleEvent() cycles the backgroundIndex variable to the next color value within the color[] array and sets the background to this value using the setBackground() method. It then invokes the repaint() method of the Component class to cause the screen to be repainted. The Foreground button is handled in a similar manner.

When the Font button is clicked, handleEvent() cycles the fontIndex to the next font name and creates a new 14-point plain font of that type. It then invokes the setFont() method of the Component class to change the current font. The repaint() method is used to cause the screen to be repainted.

Using Layouts

The FrameApp program uses a number of panels and layouts to organize the way that labels and buttons are presented in the application window. The organization of any object that is a subclass of the Container class is governed by a layout. The layout determines how objects of class Component are positioned when they are added via the add() method to the Container object. Five types of layouts are provided by Java: BorderLayout, CardLayout, FlowLayout, GridLayout, and GridBagLayout. Other, custom layouts can also be defined.

The BorderLayout class is the default layout used by Frame objects. An object of the Component class is added to either the North, South, East, West, or Center of the component, as shown in Figure 19.7.

Figure 19.7 : A BorderLayout example.

In the FrameApp program, the mainPanel was added to the South region of the window. The remainder of the program window was used by the program's default Graphics object, which was placed in the North region of the window. No Component objects were added to the East, West, or Center regions.

The FlowLayout class is the default layout used for Panel objects. If a container uses a FlowLayout, the container is filled left to right from top to bottom. An example of this layout is the two rows of buttons that were added to panel1 and panel2. The FlowLayout class causes each component to be centered in its container, by default.

The GridLayout class organizes a container as a grid of n rows and m columns. Each grid cell is the same size, as shown in Figure 19.8.

Figure 19.8 : A Gridlayout example.

The GridLayout class is used with the mainPanel in the FrameApp program. A grid of four rows and one column is used to stack panel1 and panel2 with the two labels to produce the display shown in Figure 19.2.

The CardLayout class organizes a container like a deck of cards. The first component in the container is initially displayed. Other components are then displayed using the next(), previous(), first(), last(), and show() methods of the CardLayout class. The CardLayout class is illustrated in Listing 19.2.

The GridBagLayout class is the most complex and flexible of the layout classes. It is similar to the GridLayout class in that it organizes its components in a grid, but it is more flexible because it allows the rows and columns to have different sizes. In addition, components are allowed to span multiple rows and columns. The positioning of each component is controlled by the use of objects of class GridBagConstraints. The GridBagConstraints objects identify the preferred size of each component and specify constraints on how they should be laid out. You should refer to the API documentation for the GridBagLayout and GridBagConstraints classes to read the detailed description of the variables and methods of these classes. The GridBagLayout class is also demonstrated in the LayoutApp program in Listing 19.2.

The LayoutApp program illustrates the use of each of the five predefined Java layouts. These layouts position buttons within panels to show how the various layouts are organized and displayed. A Panel pull-down menu is used to switch to each of the layout classes.


Listing 19.2. The source code of the LayoutApp program.

import java.awt.*;

public class LayoutApp extends Frame {
 MenuBar menuBar;
 Panel panels[];
 Panel currentPanel;
 static int border=0;
 static int card=1;
 static int flow=2;
 static int grid=3;
 static int gridBag=4;
 Menu cardMenu;
 public static void main(String args[]){
  LayoutApp app = new LayoutApp();
 }
 public LayoutApp() {
  super("BorderLayout");
  setup();
  pack();
  resize(400,400);
  show();
 }
 void setup() {
  setupMenuBar();
  setupPanels();
 }
 void setupMenuBar() {
  menuBar = new MenuBar();
  Menu fileMenu = new Menu("File");
  fileMenu.add(new MenuItem("Exit"));
  menuBar.add(fileMenu);
  Menu panelMenu = new Menu("Panel");
  panelMenu.add(new MenuItem("BorderLayout"));
  panelMenu.add(new MenuItem("CardLayout"));
  panelMenu.add(new MenuItem("FlowLayout"));
  panelMenu.add(new MenuItem("GridLayout"));
  panelMenu.add(new MenuItem("GridBagLayout"));
  menuBar.add(panelMenu);
  cardMenu = new Menu("Card");
  cardMenu.add(new MenuItem("First"));
  cardMenu.add(new MenuItem("Last"));
  cardMenu.add(new MenuItem("Next"));
  cardMenu.add(new MenuItem("Previous"));
  setMenuBar(menuBar);
 }
 void setupPanels() {
  panels = new Panel[5];
  for(int i=0;i<5;++i) panels[i]=new Panel();
  panels[border].setLayout(new BorderLayout());
  panels[card].setLayout(new CardLayout());
  panels[flow].setLayout(new FlowLayout());
  panels[grid].setLayout(new GridLayout(2,3));
  GridBagLayout gridBagLayout = new GridBagLayout();
  panels[gridBag].setLayout(gridBagLayout);
  panels[border].add("North",new Button("North"));
  panels[border].add("South",new Button("South"));
  panels[border].add("East",new Button("East"));
  panels[border].add("West",new Button("West"));
  panels[border].add("Center",new Button("Center"));
  String cardButtons[] = {"First","Second","Third","Fourth","Last"};
  String flowButtons[] = {"One","Two","Three","Four","Five"};
  String gridButtons[] = {"(0,0)","(1,0)","(2,0)","(0,1)","(1,1)","(2,1)"};
  for(int i=0;i<cardButtons.length;++i)
   panels[card].add(new Button(cardButtons[i]));
  for(int i=0;i<flowButtons.length;++i)
   panels[flow].add(new Button(flowButtons[i]));
  for(int i=0;i<gridButtons.length;++i)
   panels[grid].add(new Button(gridButtons[i]));
  Button gridBagButtons[] = new Button[9];
  for(int i=0;i<9;++i) gridBagButtons[i] = new Button("Button"+i);
  int gridx[] = {0,1,2,0,2,0,1,1,0};
  int gridy[] = {0,0,0,1,1,2,2,3,4};
  int gridwidth[] = {1,1,1,2,1,1,1,2,3};
  int gridheight[] = {1,1,1,1,2,2,1,1,1};
  GridBagConstraints gridBagConstraints[] = new GridBagConstraints[9];
  for(int i=0;i<9;++i) {
   gridBagConstraints[i] = new GridBagConstraints();
   gridBagConstraints[i].fill=GridBagConstraints.BOTH;
   gridBagConstraints[i].gridx=gridx[i];
   gridBagConstraints[i].gridy=gridy[i];
   gridBagConstraints[i].gridwidth=gridwidth[i];
   gridBagConstraints[i].gridheight=gridheight[i];
   gridBagLayout.setConstraints(gridBagButtons[i],gridBagConstraints[i]);
   panels[gridBag].add(gridBagButtons[i]);
  }
  add("Center",panels[border]);
  currentPanel=panels[border];
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.WINDOW_DESTROY){
   System.exit(0);
   return true;
  }else if(event.id==Event.ACTION_EVENT){
   if(event.target instanceof MenuItem){
    if("Exit".equals(event.arg)){
     System.exit(0);
     return true;
    }else if("BorderLayout".equals(event.arg)){
     switchPanels(panels[border],"BorderLayout",false);
     return true;
    }else if("CardLayout".equals(event.arg)){
     switchPanels(panels[card],"CardLayout",true);
     return true;
    }else if("FlowLayout".equals(event.arg)){
     switchPanels(panels[flow],"FlowLayout",false);
     return true;
    }else if("GridLayout".equals(event.arg)){
     switchPanels(panels[grid],"GridLayout",false);
     return true;
    }else if("GridBagLayout".equals(event.arg)){
     switchPanels(panels[gridBag],"GridBagLayout",false);
     return true;
    }else if("First".equals(event.arg)){
     CardLayout currentLayout=(CardLayout)currentPanel.getLayout();
     currentLayout.first(currentPanel);
     return true;
    }else if("Last".equals(event.arg)){
     CardLayout currentLayout=(CardLayout)currentPanel.getLayout();
     currentLayout.last(currentPanel);
     return true;
    }else if("Next".equals(event.arg)){
     CardLayout currentLayout=(CardLayout)currentPanel.getLayout();
     currentLayout.next(currentPanel);
     return true;
    }else if("Previous".equals(event.arg)){
     CardLayout currentLayout=(CardLayout)currentPanel.getLayout();
     currentLayout.previous(currentPanel);
     return true;
    }
   }
  }
  return false;
 }
 void switchPanels(Panel newPanel,String newTitle,boolean setCardMenu) {
  remove(currentPanel);
  currentPanel=newPanel;
  add("Center",currentPanel);
  setTitle(newTitle);
  if(setCardMenu) menuBar.add(cardMenu);
  else menuBar.remove(cardMenu);
  show();
 }
}


When you compile and run LayoutApp, the opening window should look like the one shown in Figure 19.9.

Figure 19.9 : The opening window of the LayoutApp program.

The opening window illustrates the use of the BorderLayout class by displaying a panel that uses the BorderLayout. Notice that the window's title is BorderLayout. The title is updated when a new layout class is displayed. To switch layouts, use the Panel pull-down menu as shown in Figure 19.10.

Figure 19.10 : The Panel menu.

Select the CardLayout menu item from the Panel menu. The window displays a panel that uses a CardLayout object, and the window's title is updated to identify the new layout. An additional Card pull-down menu is added to the menu bar so that the different button components in the CardLayout object can be displayed. (See Figure 19.11.) There are five buttons, labeled First, Second, Third, Fourth, and Last. Use the Next menu item of the Card menu to step through these buttons. After you have reached the button labeled Last, use the Previous menu item to step back through the list of buttons. Next, try using the First and Last menu items to go to the first and last buttons in the panel governed by the CardLayout object.

Figure 19.11 : A CardLayout example.

Select the FlowLayout menu item from the Panel menu. The window displays five buttons, labeled One through Five, across the top of the window as shown in Figure 19.12. The Card pull-down menu is removed and the window's title is changed to FlowLayout.

Figure 19.12 : A FlowLayout example.

Select the GridLayout menu item from the Panel menu. The window displays six buttons in a rectangular grid that is two rows by three columns, with each button labeled with its x,y-coordinate, as shown in Figure 19.13. The window's title is changed to GridLayout.

Figure 19.13 : A GridLayout example.

Select the GridBagLayout menu item from the Panel menu. The window displays nine buttons arranged in a free-form manner in a rectangular grid, five rows by three columns, as shown in Figure 19.14. The window's title is changed to GridBagLayout.

Figure 19.14 : A GridBagLayout example

Now that you have an idea of what the LayoutApp program does, let's see how it works.

The LayoutApp class declares several variables and constants. The menuBar variable provides the program's menu bar. It is updated by different class methods when it is initially set up and displayed and when the Card menu is added and removed. The cardMenu variable is also declared. The panels[] array consists of five panels that illustrate the five predefined Java layouts. It is indexed by the border, card, flow, grid, and gridBag constants. The currentPanel variable is used to keep track of the current panel being displayed.

The main() function should seem to be pretty standard to you by now. It is typical of the main() function found in most window programs.

The LayoutApp class invokes the superclass constructor with the BorderLayout title. BorderLayout is the layout of the first panel to be displayed and is the default layout for Frame objects. The setup() method is invoked to set up the menu bar and panels used in the program. The rest of the LayoutApp constructor is fairly standard. The pack(), resize(), and show() methods are used to organize, resize, open, and display the LayoutApp main window.

The setupMenuBar() method creates a new menu bar and assigns it to the menuBar variable. A File menu is created with an Exit menu item and is then added to the object referenced by menuBar. A Panel menu is created and added to the menuBar in a similar fashion. The Panel menu is given buttons that identify each of the five layouts. A third Card menu is also created, but is not added to the menuBar. It is given the First, Last, Next, and Previous menu items. Finally, the object referenced by menuBar is set as the menu bar by invoking the setMenuBar() method of the Frame class.

The setupPanels() method is the most complicated method in this program. It is where all of the five different panels are created and laid out. First, the panels[] array is allocated and then five new Panel objects are assigned to the array's elements. Then each of the panels is laid out.

The panels[border] array element has its layout set to a BorderLayout object using the setLayout() method of the Container class. The panels[card] array element has its layout set to a CardLayout object. The panels[flow] array element has its layout set to a FlowLayout object. The panels[grid] array element has its layout set to a GridLayout object, two rows by three columns. The layout of the panels[gridBag] array element is separately created and assigned to the gridBagLayout variable. It is used later on when the panel is laid out with objects of class GridBagConstraints.

After each of the panels has had its layout set, buttons are added to illustrate how the individual layouts are displayed. The panels[border] panel is given five buttons, labeled North, South, East, West, and Center. These buttons are added to the positions identified by their labels. The buttons for the panels[card], panels[flow], and panels[grid] panels are added by first creating an array of labels for each panel and then using a for statement to step through the arrays, creating buttons and adding them to their respective panels. This approach greatly simplifies the process of creating buttons and adding them to panels.

The buttons for the panels[gridBag] panel are created a little differently by iterating from 0 through 8 and appending each number to the end of the "Button" string. The gridx[], gridy[], gridwidth[], and gridheight[] arrays are used to produce the objects of class GridBagConstraints that are used to lay out the buttons in the panels[gridBag] panel. The gridx[] array identifies the x-coordinate of each button within the 5-by-3 grid. The gridy[] array identifies they-coordinate. The gridwidth[] array identifies button widths in terms of number of grid cells. The gridheight[] array identifies the height of each button in grid cells.

An array of GridBagConstraint objects is created and assigned to the gridBagConstraints variable. Each of the array's elements is then created and initialized using the gridx[], gridy[], gridwidth[], and gridheight[] arrays. The fill variable of each array element is set to the GridBagConstraints.BOTH constant, indicating that the buttons may grow both vertically and horizontally to fill the space assigned to them as the result of the way they are laid out. The setConstraints() method is used to associate each button with its gridBagConstraints[] element within the GridBagLayout object referenced by gridBagLayout. The last line in the for statement adds each button to the panels[gridBag] panel.

Finally, the panels[border] panel is assigned as the initial panel to be displayed when the program starts up. The currentPanel variable is also initialized to panels[border].

The handleEvent() method handles the processing of user menu selections. The WINDOWS_DESTROY event and the Exit menu option result in immediate program termination. The menu items associated with each layout result in the appropriate panel being selected and displayed. This is accomplished using the switchPanels() method.

The First, Last, Next, and Previous menu items are processed by getting the layout associated with the current panel and then invoking the first(), last(), next(), or previous() methods of the CardLayout class to select the appropriate button to be displayed within the panels[card] panel.

The switchPanels() method is used to provide a common method of switching from one panel to another. It begins by using the remove() method of the Container class to remove the current panel from the LayoutApp main window. The current panel is set based on the Panel object passed to the method via the newPanel parameter. The new current panel is then added to the center of the main window, and the window's title is set based on the newTitle string parameter. The cardMenu is added or removed from the menu bar based on the boolean setCardMenu parameter. Finally, the new panel is displayed by invoking the show() method of the Window class.

Connecting Code to Events

Although the various graphical user interface subclasses of the Component class are what is seen by the user when he interacts with a window program, the event-handling software is what actually connects these components to the code that implements the program's behavior.

Events are generated as the result of the user's interaction with the program's GUI components. These events are defined in the Event class and cover a large variety of user actions using the mouse and keyboard.

Until now you've been handling events using monolithic handleEvent() methods. Although this approach works, it can sometimes become difficult to manage. An alternative approach is to handle events locally by each GUI component, which allows event handling to be more closely aligned with the components generating the event. Local component event handling is performed using the handleEvent(), action(), and other methods, such as mouseUp() and mouseDown(), that are inherited from the Component class. Consult the Component class API for a detailed list of these methods. In order to perform local event handling you must subclass these components (for example, Button, Checkbox, Scrollbar) and override their event-handling methods.

The handleEvent() and action() methods are the most common methods used for event handling. You've already used the handleEvent() method quite extensively. The action() method is used to handle events that have the ACTION_EVENT type. These events are typical of GUI components such as menu items, buttons, lists, and checkboxes.

The action() method is passed the event along with an object containing the value of the event's arg variable. This object describes the Component being acted upon. For example, clicking a button results in the button's label being assigned to the arg variable. The window sampler program presented in the next section shows how the action() method is used.

The Window Sampler Program

The SamplerApp program serves a dual purpose. It shows how each of the most common GUI components are displayed and accessed and also demonstrates local event-handling approaches for each of these components. The program displays Label, TextField, TextArea, Button, Canvas, Checkbox, Choice, List, and Scrollbar objects and shows how to handle events associated with these objects. The program's source code is shown in Listing 19.3.


Listing 19.3. The source code of the SamplerApp program.

import java.awt.*;
import java.lang.System;

public class SamplerApp extends Frame {
 TextArea textArea;
 public static void main(String args[]){
  SamplerApp app = new SamplerApp();
 }
 public SamplerApp() {
  super("Windows Sampler");
  setup();
  pack();
  resize(400,400);
  show();
 }
 void setup() {
  setupMenuBars();
  setupPanels();
 }
 void setupMenuBars() {
  MenuBar menuBar = new MenuBar();
  Menu fileMenu = new Menu("File");
  fileMenu.add(new MenuItem("Exit"));
  menuBar.add(fileMenu);
  setMenuBar(menuBar);
 }
 void setupPanels() {
  Panel mainPanel = new Panel();
  mainPanel.setLayout(new GridLayout(3,3));
  Panel panels[][] = new Panel[3][3];
  for(int i=0;i<3;++i){
   for(int j=0;j<3;++j){
    panels[j][i] = new Panel();
    panels[j][i].setLayout(new FlowLayout(FlowLayout.LEFT));
   }
  }
  panels[0][0].add(new Label("Text Field:"));
  panels[0][0].add(new MyTextField("A text field.",15));
  panels[1][0].add(new Label("Text Area:"));
  textArea = new TextArea("A text area.",5,15);
  panels[1][0].add(textArea);
  panels[2][0].add(new Label("Button:"));
  panels[2][0].add(new MyButton("Blank Text Area",textArea));
  panels[0][1].add(new Label("Canvas:"));
  panels[0][1].add(new MyCanvas());
  String checkboxStrings[] = {"Checkboxes:","Java","Developer's","Guide"};
  panels[1][1].add(new MyCheckboxGroup(checkboxStrings));
  panels[2][1].add(new Label("Choices:"));
  String choiceStrings[] = {"Yes","No","Maybe"};
  panels[2][1].add(new MyChoice(choiceStrings,textArea));
  panels[0][2].add(new Label("List:"));
  String listStrings[] = {"Sleepy","Sneezy","Grumpy","Dopey","Doc",
   "Happy","Bashful"};
  panels[0][2].add(new MyList(listStrings,textArea));
  panels[1][2].setLayout(new BorderLayout());
  panels[1][2].add("Center",new Label("Horizontal Scrollbar:"));
  panels[1][2].add("South",new MyScrollbar(Scrollbar.HORIZONTAL,50,10,0,
   100,textArea));
  panels[2][2].setLayout(new BorderLayout());
  panels[2][2].add("North",new Label("Vertical Scrollbar:"));
  panels[2][2].add("East",new MyScrollbar(Scrollbar.VERTICAL,50,10,0,
   1000,textArea));
  for(int i=0;i<3;++i)
   for(int j=0;j<3;++j)
    mainPanel.add(panels[j][i]);
  add("Center",mainPanel);
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.WINDOW_DESTROY){
   System.exit(0);
   return true;
  }else if(event.id==Event.ACTION_EVENT){
   if(event.target instanceof MenuItem){
    if("Exit".equals(event.arg)){
     System.exit(0);
     return true;
    }
   }
  }
  return false;
 }
}
class MyTextField extends TextField {
 public MyTextField(String text,int columns) {
  super(text,columns);
 }
 public boolean action(Event event,Object arg) {
  String text = getText();
  setText(text.toUpperCase());
  return true;
 }
}
class MyButton extends Button {
 TextArea textArea;
 public MyButton(String text,TextArea newTextArea) {
  super(text);
  textArea = newTextArea;
 }
 public boolean action(Event event,Object arg) {
  textArea.setText("");
  return true;
 }
}
class MyCanvas extends Canvas {
 int x = -1;
 int y = -1;
 int boxSize = 10;
 public MyCanvas() {
  super();
  resize(75,75);
  setBackground(Color.white);
  setForeground(Color.red);
  show();
 }
 public boolean mouseDown(Event event,int xClick,int yClick) {
  x = xClick;
  y = yClick;
  repaint();
  return true;
 }
 public void paint(Graphics g) {
  setBackground(Color.white);
  setForeground(Color.red);
  if(x>=0 && y>=0) g.fillRect(x,y,boxSize,boxSize);
 }
}
class MyCheckboxGroup extends Panel {
 String labelString;
 String checkboxLabels[];
 Checkbox checkboxes[];
 int numBoxes;
 TextField results;
 public MyCheckboxGroup(String strings[]) {
  super();
  labelString = strings[0];
  numBoxes = strings.length-1;
  checkboxLabels = new String[numBoxes];
  for(int i=0;i<numBoxes;++i)
   checkboxLabels[i] = strings[i+1];
  results = new TextField("",15);
  setupPanel();
  show();
 }
 void setupPanel() {
  setLayout(new GridLayout(numBoxes+2,1));
  add(new Label(labelString));
  checkboxes = new Checkbox[numBoxes];
  for(int i=0;i<numBoxes;++i){
   checkboxes[i] = new Checkbox(checkboxLabels[i]);
   add(checkboxes[i]);
  }
  add(results);
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.ACTION_EVENT){
   if(event.target instanceof Checkbox){
    String newResults = "";
    for(int i=0;i<numBoxes;++i)
     if(checkboxes[i].getState())
      newResults = newResults + " " +checkboxes[i].getLabel();
    results.setText(newResults);
   }
  }
  return false;
 }
}
class MyChoice extends Choice {
 TextArea text;
 public MyChoice(String strings[],TextArea textArea) {
  super();
  try {
   for(int i=0;i<strings.length;++i)
    addItem(strings[i]);
   text = textArea;
  }catch(NullPointerException ex){
   System.exit(0);
  }
 }
 public boolean action(Event event,Object arg) {
  text.setText((String)arg);
  return true;
 }
}
class MyList extends List {
 TextArea text;
 public MyList(String strings[],TextArea textArea) {
  super(3,false);
  for(int i=0;i<strings.length;++i)
   addItem(strings[i]);
  text = textArea;
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.ACTION_EVENT){
   text.setText("Double-clicked:\n "+event.arg.toString());
   return true;
  }else if(event.id==Event.LIST_SELECT){
   text.setText("Selected:\n "+
    getItem((new Integer(event.arg.toString())).intValue()));
   return true;
  }else if(event.id==Event.LIST_DESELECT){
   text.setText("Deselected:\n "+
    getItem((new Integer(event.arg.toString())).intValue()));
   return true;
  }
  return false;
 }
}
class MyScrollbar extends Scrollbar {
 TextArea text;
 public MyScrollbar(int orientation,int value,int visible,int min,int max,
  TextArea textArea) {
  super(orientation,value,visible,min,max);
  text=textArea;
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.SCROLL_LINE_UP){
   text.setText("Position: "+getValue());
   return true;
  }else if(event.id==Event.SCROLL_LINE_DOWN){
   text.setText("Position: "+getValue());
   return true;
  }else if(event.id==Event.SCROLL_PAGE_UP){
   text.setText("Position: "+getValue());
   return true;
  }else if(event.id==Event.SCROLL_PAGE_DOWN){
   text.setText("Position: "+getValue());
   return true;
  }else if(event.id==Event.SCROLL_ABSOLUTE){
   text.setText("Position: "+getValue());
   return true;
  }
  return false;
 }
}


After compiling and running the program, the main application window should be displayed, as shown in Figure 19.15. The program presents a number of GUI components at various locations within the window. Labels are used to identify these components.

Figure 19.15 : The SamplerApp program display.

The TextField object presented in the upper left corner of the window allows a user to type some text. When the user presses the Enter key from within the text field, an ACTION_EVENT is generated and the text is automatically converted to uppercase.

The TextArea object allows the user to type in and edit text in a multiline text field. This object is used in the SamplerApp program to display the results of operations on other objects such as the Blank Text Area button, the choices list, the scrollable list, and the horizontal and vertical scrollbars.

The Blank Text Area button causes all text displayed in the TextArea object to be erased. The Canvas object displays a red square at the point where the user clicks within the canvas. When the Checkbox objects are selected, they display the text of their labels in the underlying text field. The value selected from the Choice and List objects is displayed in the TextArea object. The horizontal and vertical scrollbars also display their scrollbar positions in the TextArea object.

Play around with each of the GUI components to familiarize yourself with their operation before moving on to analyze the SamplerApp source code.

The SamplerApp class has one field variable-the textArea variable that is used to hold the TextArea object written to by several GUI components. The main() method and SamplerApp constructor are defined in the standard manner. The setupMenuBars(), setupPanels(), and handleEvent() methods provide the primary processing for this class, with the bulk of the processing being performed in setupPanels().

The setupMenuBars() method creates a File menu with an Exit menu item. The handleEvent() method handles the WINDOW_DESTROY event and the Exit menu item. All other event handling is performed locally by the window components.

The setupPanels() method creates a Panel with a 3-by-3 GridLayout and assigns it to mainPanel. It then declares and initializes a 3-by-3 panels[][] array to hold the nine subpanels of the mainPanel. The layout of elements of the panels[][] array is set to a left-justified FlowLayout. Subsequent code adds GUI components to each of the panels[][] elements.

A TextField: label and an object of MyTextField are added to panels[0][0]. The MyTextField object is assigned a default value of "A text field." and is set to 15 columns. A TextArea: label and a 5-row by 15-column TextArea object with the default text of "A text area." are added to panels[1][0]. A Button: label and a MyButton object are added to panels[2][0]. The MyButton object is given the Blank Text Area label and is passed the name of a TextArea to be updated.

A Canvas: label and a MyCanvas object are added to panels[0][1]. An array of strings is created and passed to the MyCheckBoxGroup() constructor. The resulting MyCheckBoxGroup object is added to panels[1][1]. A Choices: label is added to panels[2][1]. A MyChoice object is created using the choiceStrings[] array and the textArea variable. The object is also added to panels[2][1].

A List: label is added to panels[0][2]. A MyList object is created using the names of the Seven Dwarfs and the textArea variable and is added to panels[1][2]. The layouts for panels[1][2] and panels[2][2] are changed to a BorderLayout object. A Horizontal Scrollbar: label is added to the center of panels[1][2], and a MyScrollBar object is created and added to the South region of panels[1][2]. A Vertical Scrollbar: label is added to the North region of panels[2][2], and a MyScrollBar object is created and added to the East region of panels[2][2].

After the components of all the panels have been created and added to their respective panels, each of the elements of the panels[][] array is added to the mainPanel object. The mainPanel is then added to the center of the SamplerApp window.

After the SamplerApp class is declared, seven new classes are declared that subclass the standard GUI components and provide custom display and event handling.

MyTextField

The TextField class provides the capability for the user to enter and edit a single line of text. The MyTextField class extends the TextField class and handles the event generated when the user presses the Enter key while editing within the text field. The MyTextField() constructor passes the text and columns parameters to the TextField constructor via the superclass constructor call statement. The text parameter identifies a string of text that is to be initially displayed within the text field. The columns parameter specifies the displayed width of the text field in character columns. The action() method handles the event occurring when the user presses the Enter key while editing the text field. It uses the getText() method inherited from the TextComponent class to retrieve the current text displayed within the text field, converts it to uppercase, and then sets the converted text in the text field using the setText() method inherited from TextComponent. The TextComponent class is the parent of both TextField and TextArea.

MyButton

The MyButton class extends the Button class. Its constructor takes two parameters: the string label to be displayed on the button and a reference to the TextArea object that is to be cleared when the button is clicked. The action() method handles the button click and uses the setText() method of TextComponent to set the text of the TextArea object to an empty string.

MyCanvas

The Canvas class provides the capability to add individual drawing components to a container. It is covered extensively in Chapter 23, "The Canvas." The MyCanvas class extends Canvas and provides a minimal drawing capability. It declares the x and y variables to record the last position in which the mouse is clicked within the canvas. Their default values are set to -1 to indicate that the mouse has not yet been clicked on the canvas. The boxSize variable specifies the size of the box to be displayed at the position of the last mouse click.

The MyCanvas constructor sets the canvas size to an area of 75¥75 pixels with a white background and a red foreground drawing color. The show() method is used to cause the canvas to be initially displayed.

The mouseDown() method overrides the method defined in the Component class. It provides the capability to handle the MOUSE_DOWN event generated when the user clicks a mouse button. Java assumes a one-button mouse to provide the widest compatibility. The mouseDown() method stores the position of the user's click and then invokes the repaint() method to repaint the canvas.

The paint() method declared for MyCanvas checks to make sure that a click has occurred and fills a rectangle with the upper left corner at the point of the last click and with boxSize dimensions.

MyCheckBoxGroup

The MyCheckBoxGroup class extends the Panel class. It implements a custom panel consisting of a label, an array of Checkbox objects, and a TextField object. The results of clicking on any of the checkboxes are displayed in the results TextField. The MyCheckBoxGroup constructor takes an array of strings as its parameter. It sets the label string to the first string in the array and sets the labels of the checkboxes to the rest of the strings. The numBoxes variable specifies the number of Checkbox objects to be created. The results TextField is created as an empty 15-character TextField object.

The setupPanel() method sets the layout of the panel to a GridLayout that is one column wide and has enough rows to accommodate the label, checkboxes, and text field. The label is created and added to the panel, followed by each of the checkboxes. The results TextField is then added to the end of the panel.

The MyCheckBoxGroup handleEvent() method is used to handle any actions occurring within the panel and is capable of handling all events for all checkboxes. It handles checkbox events by using the getState() and getLabel() methods of the Checkbox class to query the checked status of each checkbox and to retrieve the labels associated with the checkboxes that are checked. It then displays these labels in the results TextField using the setText() method.

MyChoice

The Choice class is used to implement Motif option menus, which are free-standing pull-down menus that can be used to select a single value from a list. The MyChoice class extends the Choice class and provides the capability to display selected choices in a TextArea object.

The MyChoice constructor takes an array of strings to be used as the choices and a TextArea object as its parameters. The addItem() method of the Choice class is used to add the choice strings to the list of choices. The constructor checks for a NullPointerException when the strings are added to the Choice list because the addItem() method throws this exception.

The action() method handles the event generated when a user makes a selection from the list. The arg parameter contains the label of the selected choice. It is displayed in the TextArea object using the setText() method.

MyList

The List class implements scrollable lists from which a user can select one or more list items. The MyList class extends the List class and provides support for displaying the selected list items in a TextArea field.

The MyList constructor takes an array of strings and the TextArea object as its parameters. The strings are used as the items of the list. The super(3,false) superclass constructor call statement invokes the List class constructor and specifies a three-row list with multiple list selections being disabled. Lists are covered in more detail in Chapter 21, "Checkboxes, Choices, and Lists." The addItem() method of the List class is used to add the strings as items of the list.

The handleEvent() method handles the ACTION_EVENT occurring when a list item is double-clicked, the LIST_SELECT event that is generated when an unselected list item is clicked, and the LIST_DESELECT event that occurs when a selected list item is clicked and deselected. The Windows 95 implementation of Java does not correctly handle the LIST_DESELECT event. The handleEvent() method handles these events by writing the results of the action in the specified TextArea object.

MyScrollbar

The Scrollbar class encapsulates vertical and horizontal scrollbars. The MyScrollbar class extends Scrollbar and provides the capability to display the results of scrollbar operations using a TextArea object. (See Chapter 24, "Scrollbars," for more information on using scrollbars.)

The MyScrollbar constructor takes a number of parameters that determine the characteristics of a scrollbar. These parameters are forwarded to the superclass constructor. A TextArea object is also passed as a parameter. The orientation parameter is set to the HORIZONTAL and VERTICAL constants of the Scrollbar class. These constants specify whether the scrollbar should be displayed horizontally or vertically. The min and max parameters specify a range of integer values that are associated with the scrollbar. The value parameter sets the initial position of the scrollbar between the min and max values. The visible parameter identifies the size of the visible portion of the scrollable area. This determines how the current scrollbar position is updated as the result of a page-up or page-down scrollbar operation.

The handleEvent() method of the MyScrollbar class handles the SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, and SCROLL_ABSOLUTE events. The SCROLL_LINE_UP and SCROLL_LINE_DOWN events are generated when the user clicks on the end arrows of the scrollbar. The SCROLL_PAGE_UP and SCROLL_PAGE_DOWN events are generated when the user clicks between the end arrows and the scrollbar position marker. The SCROLL_ABSOLUTE event is generated when the user moves the scrollbar position marker. The handleEvent() method handles these events by displaying the current scrollbar position in the designated TextArea object. The getValue() method of the Scrollbar class is used to obtain the current scrollbar position.

Summary

This chapter covers the basics of writing window programs. It shows how window programs are structured and organized and identifies the basic approach used to design most window programs. It covers the details of the Frame class and the five basic window layouts. The process of window event handling is described and illustrated through the SamplerApp program. SamplerApp also introduces the most common window GUI controls. Subsequent chapters investigate these components in more detail.