All Categories :
Java
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.
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 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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.

Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.