Introduction
Two of my colleagues, Susan Merritt and Allen Stix, once wrote a technical report on programming in OWL 2.5 using C++ for Windows machines. They show the user how easy graphical user interface (GUI) programming can be in C++ using a library like Borland's OWL. I've been programming in Java for a while now, and I wondered how easy (or hard) it would be to do something similar in Java. This is the result of my investigation. The code shown here was originally written in one day, though I've spent another day trying to extend it in various ways.
This is the third version of the paper. The second version used the 1.1 version of the AWT and a somewhat controversial event modeling structure that I have now abandoned.
This version uses the Java Foundation Classes (JFC) including Swing. This library has many capabilities that go far beyond what we do here. We only intend to show basic concepts that form a base for your later explorations. JFC provides many new components that serve as replacements for older (AWT) functionality. However, it doesn't completely obsolete the older libraries. The AWT has many parts that we still need here (Fonts, Event handling...) as well as providing simpler functionality that works well in many projects.
The promise of Java is that it will let the developer program an application once, on any development platform that they choose, and deploy the compiled application so that users may use it on their own computers without modification or even recompilation.
Java further promises that the "look and feel" of the application will be familiar to the user, independent of the developer's platform. Java can in fact emulate the look and feel of most platforms. Java also has its own look and feel, called "Metal." In the new Java 2 platform, the actual look of an applet or application can be determined by the user with a technology called "plugable look and feel." The Macintosh, Windows, and Unix all have slightly different buttons, windows, etc. For example, on a Macintosh, all menus are placed in a single menu bar at the top of the screen. When you bring a document from a different application to the front, the menu bar automatically changes to one appropriate for that document. On the PC, however, menus are associated with windows and appear at the top of each window. Java can preserve this behavior, without recompiling for different platforms. In these papers we will use the Java Metal look, however.
This document will explore a few simple GUI elements, such as drawing, dialog boxes, and menus. I have attempted to keep the application as close to that of Dr's Merritt and Stix as I can. In the one place where it would have been difficult to do that, I've done something in place of it that is interesting in its own right. I hope the reader is pleased. It will also explore the Java 2 event model and show how the programmer takes care of handling events.
To use this you will need a Java 2 system. You can get a simple one for free by downloading it from http://java.sun.com. It comes complete with a compiler and run-time system, but has no development environment. You don't need the compiler to run this. Just obtain the .class files from my web site and then execute the command
java AllenFrame
I developed this using an environment called CodeWarrior from MetroWerks and another called Kawa from Allaire. You can find them on the web also. The Codewarrior environment comes with Java, C++ and Object Pascal and runs on many platforms. The academic version is quite inexpensive. Kawa runs only on the PC and is also very cheap. Because of difficulties at Apple, Java 2 is not currently supported on the Macintosh, though you can develop (but not test) there for deployment elsewhere.
True to my model, developed by Merritt and Stix, this application doesn't do anything useful. It just illustrates the kind of things that can be done to give the user some ideas about how to proceed.
Java can build two kinds of executables: applications and applets. We are going to build an application here. You are familiar with applications. You run them from a command line or a windows icon. They can open and write files. They may or may not generate graphical views of themselves.
Applets are a little different. An applet is only part of an application. It needs a wider context in which to act and live. The usual container of an applet is a web browser and applets are usually downloaded from the web and displayed in the user's browser. As such, applets have a number of important security restrictions, such a the inability to read and write files on the user's system. We are going to do a number of things in this application that would make them inappropriate (and impossible) for an applet.
Some Java Notes
Java guarantees that you don't use a variable before you initialize it. It will never let you dereference null. It will never let you assign one kind of thing to a variable of another type without checking. It will never let you cast inappropriately. As we shall see, it does windows AND it takes out the garbage.
The Application Frame
This application consists of two files: AllenFrame.java and AllenApp.java. Here is AllenFrame.java. It first imports information from three Java packages. Import is similar to C++'s #include, but doesn't imply textual inclusion. The Java compiler can read compiled versions of the classes in these packages and doesn't need to re-translate header files. The file AllenFrame.java declares a public class with the same name as the file: AllenFrame. This is required in Java.
- package jbgui;
- import java.awt.Dimension;
- import java.awt.Container;
- import java.awt.BorderLayout;
- import java.awt.event.WindowAdapter;
- import java.awt.event.WindowListener;
- import java.awt.event.WindowEvent;
- import javax.swing.JFrame;
- public class AllenFrame extends JFrame
- { public static void start(String title)
- { Dimension size;
- AllenFrame f = new AllenFrame(title);
- AllenApp a = new AllenApp(f);
- a.init();
- a.start();
- size = a.getSize();
- Container contentPane = f.getContentPane();
- contentPane.add( a, BorderLayout.CENTER);
- f.pack();
- f.setSize(size);
- f.setVisible(true);
- }
- public AllenFrame(String name)
- { super(name);
- addWindowListener(closer);
- }
- // Anonymous inner class extending WindowAdapter. Handles the window close event.
- WindowListener closer = new WindowAdapter()
- { public void windowClosing(WindowEvent e)
- { dispose();
- System.exit(0);
- }
- };
- public static void main(String args[])
- { AllenFrame.start("Allen App Java Application");
- }
- }
The AllenFrame class has two static methods, main (line 34) and start (line 10). There is also a constructor (line 23), and a single data declaration (line 28). There are no member functions. Among other things, AllenFrame is a code container. Java has no global functions so we must encapsulate each function in some class. Note that in Java, you don't need to declare something before you use it.
The first line indicates that we consider this code to be part of a package named jbgui. This helps us keep lots of classes separate from each other as only one toplevel class can have any given name within a package, but we can reuse the class name in other packages. We also import three classes from he java.awt package, three more from java.awt.event, and one from javax.swing. These imports detail the dependencies of this code on the Java libraries.
Every Java application (not applet) must have a function in some class with prototype:
public static void main(String args[])
When you run the program, this is the function that you actually call from your operating system. All our main function here does is to call the other function. To call a static function of a class you precede it by its class name and a period. Static method start is just our initialization function for this application.
public static void main(String args[])
{ AllenFrame.start("Allen App Java Application");
}
When we create a new object we call a constructor. Each constructor must always begin with another constructor call, usually that of its superclass. Here AllenFrame is said to extend (line 9) or subclass JFrame. A JFrame object is the basic application window. The string passed in becomes the name of the window. We also want to be able to close the window, when the user clicks in a close box or otherwise quits the application. This means that a "WindowEvent" will be created at the point of closing and must be handled by some object in the system. We let the AllenFrame object that we will create handle closing itself, by giving it a "WindowListener" that watches for window events and will handle this one. In the constructor we must add the WindowListener to the list of components maintained by the Frame.
public AllenFrame(String name)
{ super(name);
addWindowListener(closer);
}
The WindowListener object, closer, itself has the most complex declaration in this entire application (lines 28-33):
WindowListener closer = new WindowAdapter()
{ public void windowClosing(WindowEvent e)
{ dispose();
System.exit(0);
}
};
We are declaring closer to be a WindowListener, but creating it as a new WindowAdapter subclass object. WindowListener itself is an interface. As such it defines no code, but instead about six method prototypes. Class WindowAdapter implements all of these prototypes and therefore "implements" the interface itself. However, WindowAdapter implements all of these interface functions to do nothing. Not very useful, except that we don't want most of them to do anything. All we want is to watch for windowClosing events and handle them. We therefore extend WindowAdapter and override only the one method windowClosing.
This is an example of a Java anonymous inner class. We are defining a class but we are doing so within another class (AllenFrame). We aren't giving the new class a name, but just indicating that it extends WindowAdapter.
The body of this windowClosing method calls dispose, which is actually the dispose method of the JFrame class (not the WindowAdapter class). It calls this method without any prefix because it is defined within an inner class and from inner classes you have access to all features of the "outer" classes that contain them. This function will be called automatically when the user closes the window. The AllenFrame knows who its closer is and will call the windowClosing method of its closer when the user generates a window closing event by clicking in the close box or quitting the application. Note that dispose is not like delete in C++. Java has no concept of deletion of objects since it uses a garbage collector to take care of unusable objects. Method dispose lets the Java runtime recover resources held by the window as well as removing the visual representation of the window from the screen.
Finally, the start method of the AllenFrame class creates a new AllenFrame, which is a JFrame, of course, then creates a new AllenApp and initializes and starts the AllenApp. It then adds the AllenApp to the AllenFrame as one of its components. This component will be the main part of the frame, the window contents, as we shall see. We then pack the components of f, and give it a size equal to the size of the contained AllenApp. Then we set it to be visible.
When we add the AllenApp to the AllenFrame, we indicate that it is to be in the center of the layout. Since that is the only thing we add, it doesn't much matter, but we could add it to the top (BorderLayout.NORTH) or the bottom (SOUTH) or to the East or West, if we had more components to add.
We will examine the init method of AllenApp shortly. This sets up the GUI of the application. The start method just starts it in action so that user generated events can be handled. If you forget to set the size of an application, it might wind up as a very tiny window. Method pack rearranges the components of the frame internally and squeezes out extra space.
Every "visible" application needs a frame and if it is to be a GUI application we must create the user interface and handle events in it. This we are deferring to the AllenApp class, rather than doing it directly in the AllenFrame class. This is a pretty typical programmer style.
Speaking of style: If you see a name in Java that starts with a lower case letter, it is probably a variable or function name. If it begins with a capital letter, it is probably a Class name. If it written in all capitals, it is most likely a constant. My personal style is to write Java (as well as C++) so that all grouping symbols such as braces and parentheses always match up either horizontally (on the same line) or exactly vertically. This helps me see where things begin and end.
The Application GUI
The file AllenApp.java that defines the graphical user interface defines one public class AllenApp and eleven inner classes. These inner classes are all event handlers. Ten of them are very similar since they handle similar kinds of events. Most of the code is just initialization in the init method of AllenApp. There is also a constructor, one additional method, and a few data definitions for things that must be seen by more than one function or more than one object. Note that our application consists of exactly one AllenFrame object, exactly one AllenApp object and about forty other objects. There are a few JMenuItem objects, one JMenuBar, a few other things. We will also create one new Color object and one new Font object. We will also use a few objects, like a Toolkit that we don't create ourselves. but which are part of the Java infrastructure.
Again, we will put this class into the same package (jbgui) and we will import several classes from the java libraries. Note that we could have had just three import statements, saying, for example, import java.awt.*, but then our dependencies wouldn't be explicit and that would impede us in updating this for the next version, in which the libraries may change.
- package jbgui;
- import java.awt.Image;
- import java.awt.Toolkit;
- import java.awt.Color;
- import java.awt.Event;
- import java.awt.Font;
- import java.awt.Dimension;
- import java.awt.Graphics;
- import java.awt.Rectangle;
- import java.awt.BorderLayout;
- import java.awt.event.ActionListener;
- import java.awt.event.ActionEvent;
- import java.awt.event.MouseAdapter;
- import java.awt.event.MouseListener;
- import java.awt.event.MouseEvent;
- import java.awt.event.KeyEvent;
- import java.awt.event.InputEvent;
- import javax.swing.JFrame;
- import javax.swing.JMenuBar;
- import javax.swing.JMenuItem;
- import javax.swing.JMenu;
- import javax.swing.JApplet;
- import javax.swing.KeyStroke;
- import javax.swing.JDialog;
- import javax.swing.JPanel;
- import javax.swing.JButton;
- import javax.swing.JLabel;
- //import javax.swing.ImageIcon;
- public class AllenApp extends JApplet
- { public AllenApp(JFrame myFrame){ this.myFrame = myFrame;}
- private JFrame myFrame;
- public void init()
- { resize(640, 480);
- getContentPane().setBackground(Color.yellow);
- // Get a menu bar.
- JMenuBar mb = myFrame.getJMenuBar();
- if(mb == null)
- { mb = new JMenuBar();
- getRootPane().setJMenuBar(mb);
- }
- // Create a menu for the menu bar.
- activitiesMenu = new JMenu("Activities");
- activitiesMenu.setMnemonic(KeyEvent.VK_A);
- mb.add(activitiesMenu);
- // Add menu items to the menu. Give each an ActionListener for its action.
- JMenuItem item = new JMenuItem("Cyan");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_C);
- activitiesMenu.add(item);
- item.addActionListener(new CyanListener());
- item = new JMenuItem("Yellow...");
- activitiesMenu.add(item);
- item.setMnemonic(KeyEvent.VK_Y);
- item.addActionListener(new YellowListener());
- item = new JMenuItem("Beep");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_E);
- activitiesMenu.add(item);
- item.addActionListener(new BeepListener());
- item = new JMenuItem("Box");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_B);
- activitiesMenu.add(item);
- item.addActionListener(new BoxListener());
- item = new JMenuItem("Picture");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_P);
- activitiesMenu.add(item);
- item.addActionListener(new PictureListener());
- item = new JMenuItem("Circle");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_R);
- activitiesMenu.add(item);
- item.addActionListener(new CircleListener());
- item = new JMenuItem("Info...");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, Event.CTRL_MASK));
- item.setMnemonic(KeyEvent.VK_I);
- activitiesMenu.add(item);
- item.addActionListener(new InfoListener());
- // Create a modeless dialog box with two buttons.
- yellowDialog = new JDialog(myFrame, "Your Wish is our command.",false);
- yellowDialog.setSize(230, 100);
- yellowDialog.setLocation(50, 50);
- yellowDialog.getContentPane().add(new JLabel("Restore Yellow Background?"), BorderLayout.CENTER);
- JPanel dialogPanel = new JPanel();
- yellowDialog.getContentPane().add(dialogPanel, BorderLayout.SOUTH);
- JButton yesButton = new JButton( "Yes");
- yesButton.addActionListener( new YesListener() );
- JButton noButton = new JButton("No");
- noButton.addActionListener(new NoListener() );
- dialogPanel.add(yesButton, BorderLayout.WEST);
- dialogPanel.add(noButton, BorderLayout.EAST);
- // Create a modal dialog box with one button.
- infoDialog = new JDialog(myFrame, "Data on Screen and Current ViewPort.",true);
- infoDialog.setLocation(50, 50);
- infoPanel = new JPanel();
- infoPanel.setFont(new Font("SanSerif", Font.PLAIN, 9));
- infoDialog.getContentPane().add(infoPanel);
- //Define the panel that will show the info in this dialog box.
- infoPanel.add("North", new JLabel("Size of the screen and the window in pixels:"));
- myToolkit = getToolkit();
- Dimension d = myToolkit.getScreenSize(); // Doesn't change as we run.
- String lab = "screen's length = " + d.width + ", screen's height = " + d.height;
- infoPanel.add(new JLabel( lab ), BorderLayout.CENTER);
- infoDialog.setSize(100 + infoPanel.getFontMetrics(infoPanel.getFont()).stringWidth(lab), 120);
- windowSize = new JLabel(""); // Will show current window size.
- infoPanel.add(windowSize, BorderLayout.SOUTH);
- JButton okButton = new JButton("OK");
- infoDialog.getContentPane().add(okButton, BorderLayout.SOUTH);
- okButton.addActionListener(new OkListener());
- // Handle mouse clicks in our window.
- addMouseListener(new MouseWatcher());
- // Get the image displayed by menu Picture.
- image = myToolkit.getImage("JBergin.jpg");
- }
- private JMenu activitiesMenu;
- private JDialog yellowDialog, infoDialog;
- private JPanel infoPanel;
- private JLabel windowSize;
- private Toolkit myToolkit;
- private Image image;
- // Inner Classes
- class CyanListener implements ActionListener // Menu Cyan
- { public void actionPerformed(ActionEvent e)
- { getContentPane().setBackground(new Color(200, 255, 255)); //pale Cyan (RGB)
- repaint();
- }
- }
- class YellowListener implements ActionListener // Menu Yellow...
- { public void actionPerformed(ActionEvent e)
- { yellowDialog.setVisible(true);
- }
- }
- class BeepListener implements ActionListener // Menu Beep
- { public void actionPerformed(ActionEvent e)
- { myToolkit.beep();
- } // We could actually play an audio clip here.
- }
- class BoxListener implements ActionListener // Menu Box
- { public void actionPerformed(ActionEvent e)
- { Graphics g = getGraphics();
- g.drawRect(120, 30, 90, 50); // x, y, width, height
- g.dispose();
- }
- }
- class PictureListener implements ActionListener // Menu Picture
- { public void actionPerformed(ActionEvent e)
- { Graphics g = getGraphics();
- g.drawImage(image, 100, 100, AllenApp.this);
- g.dispose();
- // Alternatively:
- // JLabel joe = new JLabel(new ImageIcon("JBergin.jpg"));
- // getContentPane().add(joe);
- }
- }
- class CircleListener implements ActionListener // Menu Circle
- { public void actionPerformed(ActionEvent e)
- { Graphics g = getGraphics();
- g.setColor(Color.green);
- g.drawOval(120, 80, 200, 200);
- g.dispose();
- }
- }
- class InfoListener implements ActionListener // Menu Info
- { public void actionPerformed(ActionEvent e)
- { Rectangle r = getBounds();
- windowSize.setText("window's length = " + r.width + ", window's height = " + r.height);
- infoDialog.setVisible(true);
- }
- }
- class OkListener implements ActionListener // OK button for Info dialog.
- { public void actionPerformed(ActionEvent e)
- { infoDialog.setVisible(false);
- }
- }
- class YesListener implements ActionListener // Yes button for Yellow... dialog.
- { public void actionPerformed(ActionEvent e)
- { yellowDialog.setVisible(false);
- getContentPane().setBackground(Color.yellow);
- repaint();
- }
- }
- class NoListener implements ActionListener // No button for Yellow... dialog.
- { public void actionPerformed(ActionEvent e)
- { yellowDialog.setVisible(false);
- }
- }
- 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 boolean imageUpdate(Image image, int flags, int x, int y, int w, int h)
- { if((flags & ALLBITS) != 0)
- { Graphics g = getGraphics();
- g.drawImage(image, 100, 100, AllenApp.this);
- g.dispose();
- }
- return true;
- }
- }
In spite of the fact that we aren't building an applet, the AllenApp class extends JApplet. This is because the JApplet class of Java provides a lot of infrastructure that we can use in handling events and setting up the interface. It is possible to make some programs act as either an application or an applet this way, but not this one, since it breaks some of the applet security rules. That won't get in our way here, however, since we won't be running it under any sort of applet viewer (like a browser) that enforces security policy.
The constructor of an AllenApp simply remembers who the containing frame is by saving it in a private instance variable.
public AllenApp(JFrame myFrame){ this.myFrame = myFrame;}
private JFrame myFrame;
The init method of an Applet is where you set up your GUI. We are going to create every GUI element here, though sometimes we would delay defining some of these until we knew that they were to be used. In particular, we will define two dialogs, since we are pretty sure that they will be used each time a user runs this application. In a more realistic situation, they might be created just before they were to be shown for the first time. They may or may not be discarded afterwards, depending on whether the programmer feels that they might be needed again soon or not.
We will set up a menu bar, a menu, seven menu items in that menu. Six of the menu items will have command key equivalents. All seven of the menu items will have their own action listeners that perform the action when the menu item is selected by the user. We will define two dialogs and their contents. Finally we will set up AllenApp itself with its own MouseListener to watch for mouse clicks in the main window. We will also do some preliminary work to help one of the menu items perform its task. Let's look at the parts in detail:
public void init()
{ resize(640, 480);
getContentPane().setBackground(Color.yellow);
First we resize the window to give ourselves some room: 640 pixels wide, by 480
high. The reference system starts (0,0) in the upper left corner. The x axis is
to the right and the y axis is down. The frame is filled with a Container object,
the content pane, into which we can put components and do drawing. We set the
background color of this Container to yellow.
// Get a menu bar.
JMenuBar mb = myFrame.getJMenuBar();
if(mb == null)
{ mb = new JMenuBar();
myFrame.setJMenuBar(mb);
}
Next we need a menu bar. We first check to see if our frame already has one and if not we create a new one and add it to the frame.
// Create a menu for the menu bar.
activitiesMenu = new JMenu("Activities", true);
activitiesMenu.setMnemonic(KeyEvent.VK_A);
mb.add(activitiesMenu);
Next we create a menu to put in the menu bar, give it the label "Activities" and
add it to the menu bar itself. We also make it possible to bring this menu down
using the keyboard equivalent ctrl-A, since A is te first letter in the menu name.
Note that I've used a stupid name like mb for the menu bar, since we are never
going to refer to it by name after this point in the program. It was so local,
that thinking up a good name is less important than if it were to be seen over
a larger range of statements.
Next we are going to create the seven menu items and install them in activitiesMenu. After creating the menu item we give it a ctrl key equivalent as well as a mnemonic. The ctrl key can be used whether the menu is up or down, but the mnemonic needs to be a letter in the displayed text and will only work wen the menu is down. We also give each menu item an action listener that will handle menu selections.
JMenuItem item = new JMenuItem("Cyan");
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.CTRL_MASK));
item.setMnemonic(KeyEvent.VK_C);
activitiesMenu.add(item);
item.addActionListener(new CyanListener());
Once we have the menu item, we add it to the menu. We will examine the listener (inner) class in a bit, but for now, just note that it will be used to carry out the actions defined by this item.
item = new JMenuItem("Yellow...");
activitiesMenu.add(item);
item.setMnemonic(KeyEvent.VK_Y);
item.addActionListener(new YellowListener());
This is the same as above, except that we won't give it a ctrl key equivalent. The label will be "Yellow..." (as defined in the constructor). The three dots is used to indicate that a dialog box will be shown if this option is selected. This use of ellipses (...) is just good interface design. The rest of the menu item construction is just like these. All the rest will have menu short cuts (both ctrl key equivalents and mnemonics) associated with them. We omit the details here.
After creating these menu items, our application menu bar with the Activities menu extended might look like the following on a PC if we run the application in the standard environment.
Here the user has selected the Activities menu in the menu bar. Note that the Mnemonic letters are underlined and the cntrl keys are listed for each option that has one.
Now we need to create two dialog boxes for the "Yellow..." and "Info..." menu items. Each is defined to be a part of the current frame, given a label and tagged as either modal or non-modal. The yellowDialog will not be modal, which means that the user can ignore it when it is displayed. We then size the window of the dialog and place it relative to its frame.
// Create a modeless dialog box with two buttons.
yellowDialog = new JDialog(myFrame, "Your Wish is our command.",false);
yellowDialog.setSize(200, 100);
yellowDialog.setLocation(50, 50);
Now we add the components to the dialog. First we add a textual label to the dialog and center it. Then we create a new panel to hold two buttons and add it to the dialog. We add it at the bottom. A panel is just a convenient way to group elements and guarantees that I can keep the two buttons together in the GUI.
yellowDialog.getContentPane().add(new Label("Restore Yellow Background?"), BorderLayout.CENTER);
JPanel dialogPanel = new JPanel();
yellowDialog.getContentPane().add(dialogPanel, BorderLayout.SOUTH);
Next we add two buttons to the new panel. We give them action listeners as usual. The listener classes are discussed below.
JButton yesButton = new JButton( "Yes");
yesButton.addActionListener( new YesListener() );
JButton noButton = new JButton("No");
noButton.addActionListener(new NoListener() );
dialogPanel.add(yesButton, BorderLayout.WEST);
dialogPanel.add(noButton, BorderLayout.EAST);
This next dialog is similar except that it has only one button. We want to display three labels in this dialog, so we create a panel to hold the labels, rather than buttons. The first label is known to the programmer, so it is just text. The second is intended to be the size of the user's computer screen, which the programmer can't know since the user can change it. Therefore we get the applications default Toolkit, which contains some system information and extract the screen size from it.
// Create a modal dialog box with one button.
infoDialog = new JDialog(myFrame, "Data on Screen and Current ViewPort.",true);
infoDialog.setLocation(50, 50);
infoPanel = new JPanel();
infoPanel.setFont(new Font("SanSerif", Font.PLAIN, 9));
infoDialog.getContentPane().add(infoPanel);
//Define the panel that will show the info in this dialog box.
infoPanel.add("North", new JLabel("Size of the screen and the window in pixels:"));
myToolkit = getToolkit();
Dimension d = myToolkit.getScreenSize(); // Doesn't change as we run.
String lab = "screen's length = " + d.width
+ ", screen's height = " + d.height;
infoPanel.add(new JLabel( lab ), BorderLayout.CENTER);
infoDialog.setSize(100 + infoPanel.getFontMetrics(infoPanel.getFont()).stringWidth(lab), 120);
The Label constructor requires a string. Here we have constructed a string from
some elements that are obviously strings since they are quoted. The value d.width,
however is an integer. Most Java types know how to convert themselves to strings
when needed. The plus operator here is string catenation. The third label will
be given its text later. It is the size of the program window. This was set
at 640 by 480 at initialization, but the user might change it. We also save
this label in a variable so that we can refer to it later.
windowSize = new JLabel(""); // Will show current window size.
infoPanel.add(windowSize, BorderLayout.SOUTH);
JButton okButton = new JButton("OK");
infoDialog.getContentPane().add(okButton, BorderLayout.SOUTH);
okButton.addActionListener(new OkListener());
Next we need to set up AllenApp with a MouseListener to watch for mouse clicks. This is because we want the application to track user clicks of the left and right mouse buttons. On a Macintosh, which has a mouse with only one button, the Right button is achieved by clicking with the Command key held down. UNIX systems typically have three mouse buttons, however.
// Handle mouse clicks in our window.
addMouseListener(new MouseWatcher());
Finally, one of our menu selections will show a picture. We need to fetch it before it can be seen and this takes a bit of time so we fetch it immediately so it will be ready when the user needs it. It is a picture of the author at work, by the way. In an application, the Toolkit is used to get the picture. The picture is just a standard jpeg file. It could also have been a gif file. This will complete our initialization of the applet.
// Get the image displayed by menu Picture.
image = myToolkit.getImage("JBergin.jpg");
}
Next we have the variables that define the various parts of our GUI.
private JMenu activitiesMenu;
private JDialog yellowDialog, infoDialog;
private JPanel infoPanel;
private JLabel windowSize;
private Toolkit myToolkit;
private Image image;
Once initialization is done and the "applet" started, it starts to accept events such as mouse clicks from the user. To handle these events efficiently, Java uses a registration system. When an object like a button or a menu item that might generate an event is created, it registers one or more listeners who are interested in that event. When the event occurs (button clicked) the button sends a message to all of its registered listeners. The protocols of these messages are defined in Java interfaces. For example, the ActionListener interface is
interface ActionListener { void actionPerformed(ActionEvent e); }
This registration system implements a design pattern called Observer. The listener is an Observer of the button or other component. You can learn more about design patterns, including the Observer pattern from the author's home page.
To implement this interface, a class must both declare that it does so, and implement each of the member functions that have a protocol in the interface. Here it is just one function: actionPerformed. Each of our classes will have a different actionPerformed method and so will perform a different action when the menu is selected (or the button is clicked). We will examine each of these classes in turn, as each does something interesting when its menu item is chosen. Note that all are inner classes (within AllenApp) so each can see the instance variables of that containing class. This is true even though these are private variables.
Next we discuss the various inner classes (classes defined within another class or method) that we use for our menu items and buttons. First the menu item classes. Each of the objects created from these classes must perform an action when the menu is selected or the button is clicked. This is achieved by implementing an interface ActionListener that the Java system knows about. When we implement this interface we must then implement the single method actionPerformed which we do in each of these classes. It is also necessary to let the menu item know which of several actionPerformed methods to call. It is possible in Java to share such methods. Therefore we must tell each component who its listener is. We achieve this by "adding" the listener to the component. In this case the menu item or button is its own listener. This next listener is added to the Cyan menu item at line 49.
class CyanListener implements ActionListener // Menu Cyan {
public void actionPerformed(ActionEvent e) { setBackground(new Color(200, 255, 255)); //pale Cyan (RGB) repaint(); } }
When the user selects the Cyan menu item, we wish to set the background color to a pale cyan color. This is not one of the standard colors defined in Color, as Color.yellow was. Therefore we need to create a new color. The standard color system in Java (there can be others) is RGB (Red-Green-Blue). In the RGB color system, each color is a combination of three values between 0 and 255. The first is the Red intensity, the second is Green, and the last, Blue. Pure Red is therefore (255, 0, 0). Opposite Red in the color wheel is Cyan, so it is (0, 255, 255). This is Color.cyan, but it is too bright. A paler version adds more red, but keeps the others fixed. Likewise yellow is opposite blue and magenta is opposite green. We can get 16,777,216 different colors by mixing red, green, and blue in different amounts.When the screen is repainted, the background will be cyan instead of the yellow that it is at startup.
The YellowListener is similar except that instead of setting the color directly, it show the yellowDialog to give the user a choice of setting it or not. It is added to the Yellow menu item in the initialization.
class YellowListener implements ActionListener // Menu Yellow...
{ public void actionPerformed(ActionEvent e)
{ yellowDialog.setVisible(true);
}
}
When the user selects the Yellow... menu option, we wish to display a dialog box and ask if the user really wishes to set the color back to yellow. We created this dialog in init, of course, and just need to display it here. Note that we don't do anything here about retrieving information about which button the user uses to dismiss this dialog. That is handled by the action listeners of the buttons (the buttons themselves). This dialog is not modal, so the user could simply drag it aside and continue using the application in other ways, returning to it later if desired.
class YesListener implements ActionListener // Yes button for Yellow... dialog.
{ public void actionPerformed(ActionEvent e)
{ yellowDialog.setVisible(false);
getContentPane().setBackground(Color.yellow);
repaint();
}
}
The YesListener is attached as the action listener of the Yes button in the dialog so if the user clicks this button, the actionPerformed method of this class will be called. This dismisses the dialog by setting its visibility to false, but then sets the background color of the main window back to yellow. The NoListener just dismisses the dialog but takes no other action.
class NoButton implements ActionListener // No button for Yellow... dialog.
{ public void actionPerformed(ActionEvent e)
{ yellowDialog.setVisible(false);
}
}
When
the user selects Beep from the Activities menu, the system beep is sounded.
Strange as it may seem, this is one of the things not allowed by applets. We
ask the Toolkit that we retrieved in init to sound the beep for us. This is
very simple, but we note in passing that we could play an audio clip here
instead with not much more work. We could even retrieve an audio clip from
some remote site on the internet and play it.
class BeepListener implements ActionListener // Menu Beep
{ public void actionPerformed(ActionEvent e)
{ myToolkit.beep();
} // We could actually play an audio clip here.
}
Sometimes an application needs to draw in a window. The Box menu item illustrates this. A simple rectangle is drawn. The upper corner is at the point (120, 30) as measured in pixels from the upper left corner. The width is 90, and the height 50 pixels. To draw in a window we need a Graphics object which sets a graphics context. We can get the current graphics object of an applet by calling its getGraphics method.
class BoxListener implements ActionListener // Menu Box { public void actionPerformed(ActionEvent e) { Graphics g = getGraphics(); g.drawRect(120, 30, 90, 50); // x, y, width, height g.dispose(); } }
Note that the getGraphics method is part of Applet and was inherited by AllenApp. It is not part of BoxListener, but can be seen since this class is inner to AllenApp. Any Graphics object returned by getGraphics should be eventually sent the dispose message to free up system resources associated with the graphics object.
The thing that Merritt and Stix's application did that was originally hard in Java was drawing with a pattern, like a cross hatch pattern. Instead we let the Picture menu item retrieve a jpeg file from the disk and display it in our window. Recall that in init, we actually retrieved this picture, so we need only draw it here. We need a graphics object to do the drawing and we need to give a variable holding the image, a point at which to draw it, and the ImageObserver that is interested in the image. This is our AllenApp object. In an inner class, the object of the "outer" class representing the owner of the corresponding inner object is referenced by prefixing the variable "this" with the name of the outer class. So here the AllenApp object that owns this particular PictureListener is called AllenApp.this. Note that this is a lot of scaffolding when we have only created one AllenApp object, but in general, when you might create several it is very helpful to have the inner object be able to refer to its owner in this way.
class PictureListener implements ActionListener // Menu Picture
{ public void actionPerformed(ActionEvent e)
{ Graphics g = getGraphics();
g.drawImage(image, 100, 100, AllenApp.this);
g.dispose();
// Alternatively
// JLabel joe = new JLabel(new ImageIcon("JBergin.jpg");
// getContentPane().add(joe);
}
}
As indicated in the comments above, there is an alternate way to put the picture into the application and that is to put it into a new JLabel and then add the JLabel to our window. That would be a bit different that what we have done above, however, as it would install the picture permanently in the window, rather than just drawing it. The difference would appear the next time the window needs to be redrawn. For example with this latter method, if we then change the background color, causing a repaint, then the picture would still be in place. If we just use the graphics to draw it once, it would disappear.
There was another method of AllenApp that we haven't yet discussed. Method imageUpdate is called repeatedly while an image is loading to inform the ImageObserver of the progress of the load. This is most helpful when images are loaded over the net, since this is slow. We override this method in AllenApp to delay the drawing of the picture the first time until it is completely present. This is the purpose of forming the bitwise and (&) between the flags passed in and the constant ALLBITS, which indicates that all bits have been received. Note that imageUpdate is called repeatedly. The call is made once per scan line of the loaded image. It returns true, I think to keep the producer sending more scan lines.
public boolean imageUpdate(Image image, int flags, int x, int y, int w, int h)
{ if((flags & ALLBITS) != 0)
{ Graphics g = getGraphics();
g.drawImage(image, 100, 100, AllenApp.this);
g.dispose();
}
return true;
}
The handling of the Circle menu is similar to that of Box except that we want to draw the circle in green rather than the standard black. To do this we can set the color of the graphics object to green.
class CircleListener implements ActionListener // Menu Circle
{ public void actionPerformed(ActionEvent e)
{ Graphics g = getGraphics();
g.setColor(Color.green);
g.drawOval(120, 80, 200, 200);
g.dispose();
}
}
The parameters to drawOval give the dimensions of the containing rectangle using coordinates (x, y, width, height). We could draw in any non-standard color simply by creating the color as we did with our light cyan earlier.
Our Info menu item gets the bounds rectangle of the AllenApp main window and sets the windowSize label's text to the width and height of it along with some labeling. This label is the third line of the dialog. The first two lines were given their text in init. We then display the infoDialog for the user. This dialog is modal because once it is displayed, the user could change the size of the window, in which case the information in it would be invalid.
class InfoListener implements ActionListener // Menu Info
{ public void actionPerformed(ActionEvent e)
{ Rectangle r = getBounds();
windowSize.setText
( "window's length = " + r.width + ", window's height = " + r.height
);
infoDialog.setVisible(true);
}
}
The OK button in this dialog simply hides the dialog.
class OKListener implements ActionListener // OK button for Info dialog.
{ public void actionPerformed(ActionEvent e)
{ infoDialog.setVisible(false);
}
}
Finally,
we want the user's mouse clicks to be recorded at the point on the screen where
they occur. The left button just shows "Left Click" at the point of the click.
On a Macintosh, which only has one button, this is the single mouse button. We
show a window below with a few clicks.
The right button, however, shows its clicks in a different font and color. It also shows the coordinates of the mouse click.
To handle mouse actions we use one of two listeners. The usual MouseListener handles most mouse events. It does not handle mouse motions, however, as that alone generates a lot of event traffic. For that you need a MouseMotionListener. Here we use only a MouseListener attached to the AllenApp class.
A MouseListener handles five different types of events of which we need only handle mouseClicked. Still, this is our most complex listener. This is because we need to distinguish right clicks from other clicks. It is also the only listener in which we need to examine the event object passed when one of the event handlers is called. We need to get the x and y coordinates of the click so that we can put our message at the right place on the screen. We also examine the modifiers of the event to see if they contain the constant BUTTON3_MASK. If so, we assume it is a right click, set the font and color after remembering the old values and then drawing a string with the required information at the required location.
class Mouseatcher 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();
}
}
Here we use one of the standard fonts "Serif" in italics and size 10 points.
Note that none of the drawing in the window is permanent. If you cover the window in any way or cause it to repaint, everything in it is lost. If we wanted it to be permanent, we would attach a data structure such as an ArrayList to AllenApp. Then each of the menu items that draws something, such as Box, would instead just put a simple object into this ArrayList describing what should be drawn. The method called paint would then be overridden to read all of the objects in this ArrayList and draw something in the window accordingly. Since this would be done at every repaint, the picture would be permanent. You might like an erase option, however, to get rid of some or all of the objects in the ArrayList.
Bibliography
Susan Merritt, Allen Stix, A Bottom-up Jump-Start in Windows Programming with OWL 2.5 (in Turbo C++ for Windows 4.5) or Windows Programming in C++ for Everyone. Pace University Technical Report Number 119, Sept. 1997
David Geary, Graphic Java 1.2, Mastering the JFC, Volume 1, Prentice Hall, 1999.
David Geary, Graphic Java 2, Mastering the JFC, Volume 2, Prentice Hall, 1999.
Note: In some ways the application shown here is atypical of what one normally does in Java. You can see some more typical examples on the web at http://www.gamelan.com, on my home site, http://csis.pace.edu/~bergin, and on the Pace sol site: http://sol.pace.edu/java. Most of this material is presented with source code. You can find an online version of this paper at http://sol.pace.edu/java/gui. From that version you can link to the code presented here.
Creating an Application with Code Warrior
NOTE: The following is not entirely up to date. Newer versions of Code Warrior have appeared.
To build an application with Code Warrior, you must first create a project. Select New Project... from the application File menu.
You will be given a New Project Dialog. Select the small triangle by the Java tab so that it scrolls down. Then select Java Application and finally OK.
You will be presented with a file dialog in which to enter the name of a project. Here I have entered Spreadsheet.prj. Use the usual navigation tools on your computer to find the place at which you want the corresponding folder (directory) to be placed. Here we want it within the directory javaCode.
Code Warrior will then create the project and skeletons for the files and will show you the project dialog.
You can then open and modify the file TrivialApplicaton.java by double clicking on its name in the project dialog.
/*
Trivial application that displays a string - 4/96 PNL
*/
public class TrivialApplication {
public static void main(String args[]) {
System.out.println( "Hello World!" );
try {
System.in.read(); // prevent console window from going away
} catch (java.io.IOException e) {}
}
}
The Project menu has many options for building the application, including Compile and Make. You can also run it from here.
If you don't want the application to be called TrivialApplication, then you should change the name of the class to something more suitable and choose SaveAs under the file menu. Give the file the same name as its public class. The file will be saved, and the project file and dialog will be updated accordingly. If you do change the name, there is one additional step you need to take before your application will run, however.
In the project dialog, next to the pulldown Named Java Application there is a dialog for preferences. You can click this or, alternatively select Java App Settings... from the Edit menu. You will be given a multi-part dialog.
Select Java Target and change the name of the Main Class to whatever you have named it instead of TrivialApplication. You can also use this dialog to set additional options, such as whether the application class files are to be put into a Java archive or not, and which virtual machine to use to run the application. If you are working on a Windows machine, be sure to use the Sun JDK virtual machine, because the Microsoft version is incomplete.
Once you have written, and tested your application, you are ready to deploy it to the world. You can deploy it by sending compiled code and others with different machines will be able to execute it. It will look a bit different on their machines if they don't have the same machine that you used for development, but it will look like what they are used to on their machine. For example, the buttons shown here were ovals. If you run this code on a PC, they will be rectangular.
I hope you've learned that GUI programming in a modern language can be quite straightforward. There are a few new concepts to learn, but once mastered, you get great expressive power.
Enjoy.
Thanks to David Mossakowski, Pace student, and Peter Jones of the University of Western Australia for reading, commenting on, and bug finding an early version of this paper.
AllenApp notes
Note that in the mouseClicked method we create a new Font object for every right mouse click. It is automatically deleted at the end of the execution of the method. This is very inefficient and a poor use of objects. After all, it is the "same" font each time. Better would be to create this font in init and save a reference to it in an instance variable. Then we can just setFont for our graphics object in MouseWatcher.mouseClicked.
We could do the same for our light cyan color, but it is less critical, since mouse clicks are probably more frequent than menu selections.
Instead of menu bars, we could have made activitiesMenu into a popup menu. The work would have been the same. The user could then pop the menu up by clicking in a special way (platform dependent-- control click on a Macintosh). The disadvantage of this is that it wouldn't be so easy to add key equivalents for our menu options. We would have had to create a key watcher, similar to our mouse watcher. I left some scaffolding for popup menus in the source code on the web site, but didn't print it in the paper.
Note that the same menu item can't be in both the menu bar and in a popup. But you could have both, by creating duplicate menu items.
In AlanApp.init, we created a lot of MenuItems, but didn't store them in any permanent variables. We did install references to them in the menu itself, however, so they don't disappear when init concludes.
Class ArrayList was mentioned. You can find it in java.util. It is a self adjusting array-like structure that grows as you add to it. You can get a complete data structures library, functionally equivalent to the STL from ObjectSpace. It is free and is called JGL, the Java Generic Library.
A good book for learning GUI programming in Java is Core Java by Cornell and Horstman, Prentice-Hall. Be sure to get the latest edition. It now comes in (at least) two volumes.
A good quickstart for the language itself is the Nutshell book mentioned in the bibliography. This is especially true if you already know C or C++.
Loading a picture from a local file is forbidden to an applet running within a browser or other applet viewer. If an applet downloaded from the net could read a file, it could send the information back to its source. You could however load a picture over the net from the source of the applet. If that machine is running a "picture server" (which could be written in Java) the server could deliver you a picture from any site in the world. Neither can an applet write a file on the local machine. If it could, it could destroy information there. Applications don't have these restrictions, because they aren't downloaded from the web and run on the local machine the way applets are.
I didn't do much with Fonts in the application. Java does quite a bit, however. You can retrieve various FontMetrics information. You can even ask a FontMetrics to tell you how many pixels wide a given String object is.
We could have put all the code in one file. In that case AllenApp would not be a public class, since AllenFrame needs to be since it has the main function. A file can have at most one public class. The way we did it is somewhat more general, since an AllenApp could be installed in a different type of frame than an AllenFrame and would still work correctly. Loose coupling between components is a good design strategy.
More Java Notes
Java lets you overload function names but not built-in operators.
Every function in Java is "virtual" in the sense of C++ virtual.
The sample program used interfaces quite a lot, though it didn't show any explicitly. Interfaces are critical to Java and to the programmer's understanding of how to use it well.
A simple multi-threaded chat server can be written in Java in about 150 lines of code. An applet to interact with the server (and its other users, of course) is about 100 lines of code. This includes code for sending and receiving data across the net, and code for synchronizing access to a buffer of messages so that different threads can't accidentally destroy common data. See sol.pace.edu/java for details. The current implementation of the applet has not been updated to Java 1.1, however.
In Part 2 we will examine Applet programming and a few more GUI elements.
The event pattern used here, Inner Listeners can be found in a paper on Java Events by the current author.
Follow this link to get the files discussed in these articles.
Last Updated: October 11, 2008