or
GUI Programming in Java for Everyone
Part 2 Applets
Joseph Bergin, Pace University
In this paper we will see some elements of applet GUI programming. The first part focused on applications and menus. It showed event handling for action and mouse events as well as simple drawing, dialogs, fonts, and a few other things. Now we are going to look at an applet that has many of the same features of AllenApp, but illustrates some additional events and GUI elements.
This applet consists of only one file, that defines a single class, AllenApplet, and nine inner classes to handle events. Again the running program will consist of about forty objects, though some of these may come and go. As before, the code is almost entirely initialization and event handling code.
You will probably not be able to view this applet in current browsers as most are not up to the Java 1.1 standards yet. You can use the applet viewer that comes with the Java JDK, and with most development environments, however.
Applets are intended to run within the context of a web browser or similar viewer. They are embedded in a frame created by their viewer. They may carry on a limited conversation with the viewer (via the AppletContext) but may obtain almost no information about the computer or environment that they run in. If applets are to be built and compiled by persons unknown and downloaded and run on your computer, you need confidence that they will not, indeed cannot, harm your files or computing environment. Applets may not read or write local files, and they may not connect via the web to any machine other than the machine from which they were loaded. They may read and write files on that machine, however, and if that source machine provides various servers, then an applet may take advantage of them.
From a language standpoint, an applet is an object of a class with java.applet.Applet as its superclass. The class Applet is itself a subclass of Panel, which is a self contained region of a window that can hold other components. Restrictions on applets, while known to compilers, are not enforced primarily by compilers, but by the run time systems embedded in browsers and applet viewers. Therefore the user need not worry that an applet was created by a rogue compiler and may therefore be dangerous.
Applets don't need a graphic interface, though most applets have them. Here we will show a fairly large window with four visible components: a TextArea, a TextField, a Button, and a Choice. A TextArea is a (possibly) large scrolling region for displaying text. A TextField is a single line of text, intended for user input. Buttons are used to let the user initiate actions. A choice is a "dropdown list" similar to what you have probably seen in web forms.
The applet, named AllenApplet, also has an invisible PopupMenu that can be used for additional options.
The basic idea of this applet is that the user may enter text into the TextField and then either hit the enter key or the Button. The text will then be moved to the TextArea. The choice is used to set font options in the TextArea and the PopupMenu is used to set color choices for the background. We will discuss the applet in detail. Here is the file AllenApplet.java, which defines a single public class. Note that there is no main function anywhere in this file.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class AllenApplet extends Applet
{ public void init()
{ resize(460, 280);
setBackground(Color.yellow);
Component ac = this;
while(!(ac instanceof Frame)) ac = ac.getParent();
Frame myFrame = (Frame)ac;
// Create a new popup menu to display our options
popup = new PopupMenu("Actiities");
add(popup);
// Add menu items to the menu. Give each an ActionListener for its action.
MenuItem item = new MenuItem("Cyan", new MenuShortcut(KeyEvent.VK_1));
popup.add(item);
item.addActionListener(new CyanListener());
item = new MenuItem("Yellow", new MenuShortcut(KeyEvent.VK_2));
popup.add(item);
item.addActionListener(new YellowListener());
item = new MenuItem("UserColor", new MenuShortcut(KeyEvent.VK_3));
popup.add(item);
item.addActionListener(new UserColorListener());
// Create a (non modal) dialog box with three fields and two buttons.
userColorDialog = new Dialog(myFrame, "Color Choice",false);
userColorDialog.setSize(200, 100);
userColorDialog.setLocation(50, 50);
userColorDialog.setResizable(false);
Panel dialogPanel = new Panel(); // Layout the fields and labels
userColorDialog.add("Center", dialogPanel);
dialogPanel.setLayout(new GridLayout(3, 2, 0, 2));
dialogPanel.add(new Label("Red"));
dialogPanel.add(red);
dialogPanel.add(new Label("Green"));
dialogPanel.add(green);
dialogPanel.add(new Label("Blue"));
dialogPanel.add(blue);
Panel buttonPanel = new Panel(); // Layout the buttons.
userColorDialog.add("South", buttonPanel);
Button yesButton = new Button( "Do It");
yesButton.addActionListener( new YesListener() );
Button noButton = new Button("Cancel");
noButton.addActionListener(new NoListener() );
buttonPanel.add( yesButton);
buttonPanel.add( noButton);
// Add components to the main window.
add("North", userText);
add("Center", userInput);
Button okButton = new Button("Add It");
add("South", okButton);
OkListener ok = new OkListener();
okButton.addActionListener(ok);
userInput.addActionListener(ok);
userInput.requestFocus();
fonts.addItemListener(new FontListener());
fonts.addItem("Plain");
fonts.addItem("Bold");
fonts.addItem("Italic");
add(fonts);
userText.setFont(new Font("Serif", Font.PLAIN, 12));
userText.setEditable(false);
// Handle mouse clicks in our window.
addMouseListener(new MouseWatcher());
// Handle menu shortcuts in our popup menu.
addKeyListener(new KeyWatcher());
}
Dialog userColorDialog;
private Panel dialogPanel;
TextArea userText = new TextArea(15, 60);
TextField userInput = new TextField(60);
PopupMenu popup;
TextField red = new TextField(6);
TextField green = new TextField(6);
TextField blue = new TextField(6);
Choice fonts = new Choice();
// Inner Classes
class CyanListener implements ActionListener // Menu Cyan
{ public void actionPerformed(ActionEvent e)
{ setBackground(new Color(200, 255, 255)); //pale Cyan (RGB)
repaint();
}
}
class YellowListener implements ActionListener // Menu Yellow...
{ public void actionPerformed(ActionEvent e)
{ setBackground(Color.yellow);
repaint();
}
}
class UserColorListener implements ActionListener // Menu UserColor
{ public void actionPerformed(ActionEvent e)
{ userColorDialog.setVisible(true);
red.requestFocus();
}
}
class OkListener implements ActionListener // OK button for main window.
{ public void actionPerformed(ActionEvent e)
{ String s = userInput.getText();
userText.append(s + '\n');
userInput.setText("");
}
}
class YesListener implements ActionListener // Yes button for UserColor dialog.
{ public void actionPerformed(ActionEvent e)
{ int r = (red.getText().length()==0)?0:Integer.parseInt(red.getText());
int g = (green.getText().length()==0)?0:Integer.parseInt(green.getText());
int b = (blue.getText().length()==0)?0:Integer.parseInt(blue.getText());
if(r<0) r = 0;
if(g<0) g = 0;
if(b<0) b = 0;
setBackground
( new Color ( Math.min(r,255), Math.min(g, 255), Math.min(b,255))
);
repaint();
}
}
class NoListener implements ActionListener // No button for UserColor dialog.
{ public void actionPerformed(ActionEvent e)
{ userColorDialog.setVisible(false);
}
}
class FontListener implements ItemListener // Listener for font choice
{ public void itemStateChanged(ItemEvent e)
{ int i = fonts.getSelectedIndex();
Font f = userText.getFont();
switch(i)
{ case 0: f = new Font("Serif", Font.PLAIN, 12); break;
case 1: f = new Font("Serif", Font.BOLD, 12); break;
case 2: f = new Font("Serif", Font.ITALIC, 12); break;
}
userText.setFont(f);
}
}
class KeyWatcher extends KeyAdapter
{ public void keyTyped(KeyEvent e)
{ char c = e.getKeyChar();
boolean meta = e.isMetaDown();
if (!meta) return;
switch((int)c)
{ case KeyEvent.VK_1:
setBackground(new Color(200, 255, 255)); repaint(); break;
case KeyEvent.VK_2:
setBackground(Color.yellow); repaint(); break;
case KeyEvent.VK_3:
userColorDialog.setVisible(true); red.requestFocus(); break;
}
}
}
class MouseWatcher extends MouseAdapter // Mouse click handler.
{ public void mouseClicked(MouseEvent e)
{ Graphics g = getGraphics();
int x = e.getX(), y = e.getY();
if(e.getModifiers() == InputEvent.BUTTON3_MASK) // Right button
//(Ctrl click on Mac).
{ Font font = new Font("Serif", Font.ITALIC, 10);
g.setFont(font);
g.setColor(Color.red);
g.drawString("Right Click at:(" + x + "," + y + ")", x, y);
}
else // Other buttons.
g.drawString("Left Click", x, y);
g.dispose();
}
public void mousePressed(MouseEvent e)
{ Graphics g = getGraphics();
int x = e.getX(), y = e.getY();
if(e.isPopupTrigger()) popup.show(AllenApplet.this, x, y);
g.dispose();
}
}
}
First, here is the overall look of our Applet window as shown in the applet viewer. We are about to click the Add It button which will add the new line of text as the second line of the TextArea at the top. This window is 460 by 280 pixels. In a larger window the layout of the four components would be somewhat different as we shall discuss.
The instance variables of this Applet define the various elements that need to be seen by more than one component. Note that, unlike C++, we can initialize these variables as part of their declarations. We would like to make these private, but can't due to a bug in the current implementation of inner classes. Currently, inner classes of applets can't see the private instance variables of the objects that contain them.
Dialog userColorDialog;
TextArea userText = new TextArea(15, 60);
TextField userInput = new TextField(60);
PopupMenu popup;
TextField red = new TextField(6);
TextField green = new TextField(6);
TextField blue = new TextField(6);
Choice fonts = new Choice();
We give the userText TextArea 15 lines and about 60 columns (in the current font), and the userInput TextField about 60 columns. We say "about" because the font's aren't necessarily monospaced. The other three TextFields will appear in the userColorDialog as we shall see.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
Again we start by importing the entire AWT as well as applet resources. Next comes the large initialization.
public class AllenApplet extends Applet
{ public void init()
{ resize(460, 280);
setBackground(Color.yellow);
As in the AllenApp, we set the screen size to 460 by 280 pixels and set the background color to bright yellow. Next we will search up through the containment hierarchy of this applet for the first frame that we find. We need the frame, since we are going to create a dialog window and it needs to know what frame it will be placed into. We know that there is a frame somewhere above us, since every application (even a browser) needs a frame.
Component ac = this;
while(!(ac instanceof Frame)) ac = ac.getParent();
Frame myFrame = (Frame)ac;
Next we create a new PopupMenu and give it a name. Actually the name is never displayed. A popup menu IS a menu, however, so the possibility exists that it could be installed in a menu bar. Therefore it needs a name.
// Create a new popup menu to display our options
popup = new PopupMenu("Actiities");
add(popup);
Next
we create three menu items and add them to the popup menu. They each get a
menu shortcut key and an action listener to handle the actions that must be
taken when they are selected. This is the same as what we have seen in
AllenApp.
// Add menu items to the menu. Give each an ActionListener for its action.
MenuItem item = new MenuItem("Cyan", new MenuShortcut(KeyEvent.VK_1));
popup.add(item);
item.addActionListener(new CyanListener());
item = new MenuItem("Yellow", new MenuShortcut(KeyEvent.VK_2));
popup.add(item);
item.addActionListener(new YellowListener());
item = new MenuItem("UserColor", new MenuShortcut(KeyEvent.VK_3));
popup.add(item);
item.addActionListener(new UserColorListener());
This
popup menu is shown below. To make it pop up, you press the left mouse button
while holding down a (machine dependent) key on the keyboard. On the Macintosh
it is the control key. Note that you need to press down in the background,
outside of any of the other components. This is because those other components
have no event handler that will make the popup appear. We are going to attach
a MouseListener to the applet itself to do this for us. Here we are about to
select UserColor from the menu. Note that popup menus are much more convenient
in applets than are ordinary Menus. We can also simulate something similar to
menus with Choices, however. You make a selection by highlighting one of the listed strings and
releasing the mouse key. To make no choice at all, just move the mouse outside
the popup before releasing the mouse button.
The next 21 statements create a dialog box that will be displayed when the UserColor action is chosen from the popup menu or the equivalent command key is used.
// Create a (non modal) dialog box with three fields and two buttons.
userColorDialog = new Dialog(myFrame, "Color Choice",false);
userColorDialog.setSize(200, 100);
userColorDialog.setLocation(50, 50);
userColorDialog.setResizable(false);
Panel dialogPanel = new Panel(); // Layout the fields and labels
userColorDialog.add("Center", dialogPanel);
dialogPanel.setLayout(new GridLayout(3, 2, 0, 2));
dialogPanel.add(new Label("Red"));
dialogPanel.add(red);
dialogPanel.add(new Label("Green"));
dialogPanel.add(green);
dialogPanel.add(new Label("Blue"));
dialogPanel.add(blue);
Panel buttonPanel = new Panel(); // Layout the buttons.
userColorDialog.add("South", buttonPanel);
Button yesButton = new Button( "Do It");
yesButton.addActionListener( new YesListener() );
Button noButton = new Button("Cancel");
noButton.addActionListener(new NoListener() );
buttonPanel.add( yesButton);
buttonPanel.add( noButton);
We first create the dialog, telling it the frame, label, and that it isn't modal. We then give it a size and a location relative to its frame, and indicate that the user may not resize it. We want a complex layout of the dialog, so we are going to use two panels, one for the three labels and three text fields, and another for the two buttons at the bottom. We create the first panel, dialogPanel and add it to the dialog. We then give the new panel a layout which is a rectangular grid of three rows and two columns. We also indicate that there is to be no space between columns, but two pixels of space between rows. We then begin to add components to the panel, by rows, first a label and then a corresponding TextField. The TextFields red, green, and blue are instance variables of the applet, recall.
Once we have all six elements of the dialogPanel, we create another buttonPanel, add it to the dialog, and add two buttons to it. Each button gets a name and a new ActionListener. These buttons are then added to the panel.
The grid layout is one of the more complex layouts that panels can have. In general, Java applets don't give you precise control over layout of components. The reason for this is that when you write an applet, you don't know much about the user's environment. The user might have an extremely small screen. Indeed some pocket PDA's (Personal Digital Assistants) have tiny screens and connections to the web. To specify a layout too carefully doesn't give the user's browser the ability to show as much detail as possible by rearranging elements.
It is possible to set components at specific pixel positions, however. To do so, you set the layout to null, and then reshape each component to it location, height and width.
If
we click the Do It button, the background color of the main window will change
to the RGB color indicated by the values in the dialog. Note that the dialog
will not be dismissed. We can't know that from looking at the construction
code, however. This is a function of the ActionListener for the button labeled
Do It. The button labeled Cancel will dismiss the box. It is non-modal,
however, so it need not be dismissed.
Note that only the background of the applet itself changes color. The components retain their original colors.
Next
we add the four components of the main window. Since the layout of an applet
(unless we change it) is a flow layout, the indications we give of "North" and
"Center" will be ignored. These read as they do, since I was experimenting
with a BorderLayout, which proved to be ugly. In the flow layout the items just
flow in in the order given. If we had an extremely wide window, they would all
be shown in a horizontal row. The TextArea and TextField appear vertically
aligned since they are wide in comparison to the window. Each of the buttons
is given an ActionListener as usual. Note that we give the TextField and the
Add It button the same listener. Thus, when the Add It button is clicked or
the user hits the enter key while typing in the userInput TextField, the effect
will be the same.
// Add components to the main window.
add("North", userText);
add("Center", userInput);
Button okButton = new Button("Add It");
add("South", okButton);
OkListener ok = new OkListener();
okButton.addActionListener(ok);
userInput.addActionListener(ok);
userInput.requestFocus();
We also request that the focus be given to the userInput field. This means that the field will be initially active so that the user need not click in the field before typing. It is ready to accept user input.
Next we will add three items to the fonts Choice, and give the Choice an ItemListener. An ItemListener handles changes in selections in Choices and in Lists. Finally we add the fonts Choice to the applet itself. If we don't do this last step, it won't appear, nor will it be able to generate any events.
fonts.addItemListener(new FontListener());
fonts.addItem("Plain");
fonts.addItem("Bold");
fonts.addItem("Italic");
add(fonts);
Next
we set the font in the userText TextArea to be one of the fonts that we will
use when the user selects from the Choice. We then set the userText area to be
uneditable. This means that it will not accept keystrokes from the user.
userText.setFont(new Font("Serif", Font.PLAIN, 12));
userText.setEditable(false);
Here
we add a mouse listener to AllenApplet as we did for AllenApp. It will
tell us where we press the mouse key, though only when doing so in the
background. It will have no effect if we click inside userText or another
component.
// Handle mouse clicks in our window.
addMouseListener(new MouseWatcher());
Note that the popup menu was given key equivalents. Those are not effective for popup menus, however. We added them so that they would have a visual look indicating the presence of command equivalents. We need to do some additional work, however, to simulate the key equivalents. Therefore we add a KeyListener to the applet. It will look for key presses in the background (but not the other components) that correspond to our command keys combinations. Whether a key press "counts" or not depends on which component has the focus. The user changes the focus by clicking somewhere. That component then gets the focus. The program can also request that the focus be given to a particular component, as we have seen.
// Handle menu shortcuts in our popup menu.
addKeyListener(new KeyWatcher());
}
Well,
that is it for initialization. All that is left of the applet is the various
listeners that handle the events generated by the components we have put into
the applet. some of these we have seen before. Again, all of them are defined
by inner classes.
The CyanListener and the YellowListener should be old friends. We have implemented a simpler YellowListener here than we did before, simply changing the color without putting up a dialog.
class CyanListener implements ActionListener // Menu Cyan
{ public void actionPerformed(ActionEvent e)
{ setBackground(new Color(200, 255, 255)); //pale Cyan (RGB)
repaint();
}
}
class YellowListener implements ActionListener // Menu Yellow...
{ public void actionPerformed(ActionEvent e)
{ setBackground(Color.yellow);
repaint();
}
}
The UserColorListener does put up a dialog; the userColorDialog we created in init. It also requests that the TextField red get the focus, so that the user can immediately begin typing. Note that it doesn't try to handle any of the input to the box. That is the job of the other listeners. In particular, the listeners associated with the two buttons in the dialog.
class UserColorListener implements ActionListener // Menu UserColor
{ public void actionPerformed(ActionEvent e)
{ userColorDialog.setVisible(true);
red.requestFocus();
}
}
The
YesListener, associated with the button labeled Do It, creates a new RGB color
and sets the applet's background to it. It has to do some error checking,
however. The user might click Do It without entering any data for a field. In
this case the text in the box will have length zero. We check for this and
assume a zero value for the field in this case. We also need to be sure that
we don't try to create a color with values outside the range 0...255 for red,
green, or blue. We have shown a few different techniques for these tests,
including the static function min of the class java.lang.Math. This class has
lots of other useful functions as well.
class YesListener implements ActionListener // Yes button for UserColor dialog.
{ public void actionPerformed(ActionEvent e)
{ int r = (red.getText().length()==0)?0:Integer.parseInt(red.getText());
int g = (green.getText().length()==0)?0:Integer.parseInt(green.getText());
int b = (blue.getText().length()==0)?0:Integer.parseInt(blue.getText());
if(r<0) r = 0;
if(g<0) g = 0;
if(b<0) b = 0;
setBackground
( new Color ( Math.min(r,255), Math.min(g, 255), Math.min(b,255))
);
repaint();
}
}
The NoListener simply dismisses the dialog. We have seen this before. Note that the YesListener does not have a command to set the visibility to false. Therefore the color dialog stays around until the user hits the Cancel button. This way the user could try various colors before dismissing the dialog.
class NoListener implements ActionListener // No button for UserColor dialog.
{ public void actionPerformed(ActionEvent e)
{ userColorDialog.setVisible(false);
}
}
Back in the main window, when the user clicks the button labeled Add It, or hits the enter key while the userInput field has the focus, we want the text in the field to be moved to the TextArea. The OkListener does this for both. It works by getting the text from the field, appending a newline to it, appending it to the text in the TextArea, and then clearing the text in the field. All quite simple. In a more realistic applet, the text entered into a field could be sent around the world or used to control some complex behavior.
class OkListener implements ActionListener // OK button for main window.
{ public void actionPerformed(ActionEvent e)
{ String s = userInput.getText();
userText.append(s + '\n');
userInput.setText("");
}
}
Our FontListener, which is an ItemListener, different from the ActionListeners we have seen above, handles changes that the user makes in the fonts Choice. When a user presses the mouse on the choice, it pops up the various choices available. The user selects one by highlighting it and releasing the mouse. We want the action to occur at this point.
class FontListener implements ItemListener // Listener for font choice
{ public void itemStateChanged(ItemEvent e)
{ int i = fonts.getSelectedIndex();
Font f = userText.getFont();
switch(i)
{ case 0: f = new Font("Serif", Font.PLAIN, 12); break;
case 1: f = new Font("Serif", Font.BOLD, 12); break;
case 2: f = new Font("Serif", Font.ITALIC, 12); break;
}
userText.setFont(f);
}
}
Since we have only one object that can generate ItemEvents we don't need to ask the ItemEvent object which object generated the event, though we could if necessary. We know it was the fonts Choice. So we ask that object which of its choices is currently selected and create a new font accordingly. We then give the userText area this new font. The effect is shown below. First we show the choice before the user selects Bold. Then we show the immediate effect.
To simulate command keys defined in the colors popup, we need a KeyListener to watch for key presses when the background has the focus. The keys are 1, 2, and 3 respectively, but these should only cause an action if the "Meta" key is also held down. On the Macintosh, this is the Command key, so there a Command-1 causes the background to switch to pale cyan. We ask the KeyEvent if the Meta key was down when the event occurred and if not, we just return without doing anything. Otherwise we check if the key was one of those of interest to us, and if so, we do the right thing. Had we named some of the other listeners, we could have sent them messages to cause these effects without rewriting them.
class KeyWatcher extends KeyAdapter
{ public void keyTyped(KeyEvent e)
{ char c = e.getKeyChar();
boolean meta = e.isMetaDown();
if (!meta) return;
switch((int)c)
{ case KeyEvent.VK_1:
setBackground(new Color(200, 255, 255)); repaint(); break;
case KeyEvent.VK_2:
setBackground(Color.yellow); repaint(); break;
case KeyEvent.VK_3:
userColorDialog.setVisible(true); red.requestFocus(); break;
}
}
}
The mouseClicked method of our MouseWatcher is identical to that of AllenApp, but note that it is only effective when we click outside all of the components of our window.
class MouseWatcher extends MouseAdapter // Mouse click handler.
{ public void mouseClicked(MouseEvent e)
{ Graphics g = getGraphics();
int x = e.getX(), y = e.getY();
if(e.getModifiers() == InputEvent.BUTTON3_MASK) // Right button
//(Ctrl click on Mac).
{ Font font = new Font("Serif", Font.ITALIC, 10);
g.setFont(font);
g.setColor(Color.red);
g.drawString("Right Click at:(" + x + "," + y + ")", x, y);
}
else // Other buttons.
g.drawString("Left Click", x, y);
g.dispose();
}
We need one additional method, however, to get the popup displayed. When the mouse is pressed while the machine dependent popup trigger is active we want the popup menu to appear. The popup trigger is usually another key. On the Macintosh it is the Control key. You will need to experiment to find it on your machine. We get the x and y coordinates of the mouse press from the MouseEvent so that we can display the popup at that location. We also pass the show method the component in which the popup will appear. This is the applet. Again, we can't just use the variable this, since it represents the MouseWatcher object, not the Applet. But since the MouseWatcher is inner to the applet, we can get the right thing with AllenApplet.this.
public void mousePressed(MouseEvent e)
{ Graphics g = getGraphics();
int x = e.getX(), y = e.getY();
if(e.isPopupTrigger()) popup.show(AllenApplet.this, x, y);
g.dispose();
}
}
}
As is true for any applet, this one needs an associated html file in which to display it. A simple one for the AllenApplet follows.
<title>Allen Applet</title>
<hr>
<applet archive="AppletClasses.jar" code="AllenApplet.class" width=460 height=280>
</applet>
<hr>
<a href="AllenApplet.java">The source.</a>