Bruce Eckel's Thinking in Java Contents | Prev | Next

Windowed applications

It’s possible to see that for safety’s sake you can have only limited behavior within an applet. In a real sense, the applet is a temporary extension to the Web browser so its functionality must be limited along with its knowledge and control. There are times, however, when you’d like to make a windowed program do something else than sit on a Web page, and perhaps you’d like it to do some of the things a “regular” application can do and yet have the vaunted instant portability provided by Java. In previous chapters in this book we’ve made command-line applications, but in some operating environments (the Macintosh, for example) there isn’t a command line. So for any number of reasons you’d like to build a windowed, non-applet program using Java. This is certainly a reasonable desire.

A Java windowed application can have menus and dialog boxes (impossible or difficult with an applet), and yet if you’re using an older version of Java you sacrifice the native operating environment’s look and feel. The JFC/Swing library allows you to make an application that preserves the look and feel of the underlying operating environment. If you want to build windowed applications, it makes sense to do so only if you can use the latest version of Java and associated tools so you can deliver applications that won’t confound your users. If for some reason you’re forced to use an older version of Java, think hard before committing to building a significant windowed application.

Menus

It’s impossible to put a menu directly on an applet (in Java 1.0 and Java 1.1; the Swing library does allow it), so they’re for applications. Go ahead, try it if you don’t believe me and you’re sure that it would make sense to have menus on applets. There’s no setMenuBar( ) method in Applet and that’s the way a menu is attached. (You’ll see later that it’s possible to spawn a Frame from within an Applet, and the Frame can contain menus.)

There are four different types of MenuComponent, all derived from that abstract class: MenuBar (you can have one MenuBar only on a particular Frame), Menu to hold one individual drop-down menu or submenu, MenuItem to represent one single element on a menu, and CheckboxMenuItem, which is derived from MenuItem and produces a checkmark to indicate whether that menu item is selected.

Unlike a system that uses resources, with Java and the AWT you must hand assemble all the menus in source code. Here are the ice cream flavors again, used to create menus:

//: Menu1.java
// Menus work only with Frames.
// Shows submenus, checkbox menu items
// and swapping menus.
import java.awt.*;

public class Menu1 extends Frame {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  TextField t = new TextField("No flavor", 30);
  MenuBar mb1 = new MenuBar();
  Menu f = new Menu("File");
  Menu m = new Menu("Flavors");
  Menu s = new Menu("Safety");
  // Alternative approach:
  CheckboxMenuItem[] safety = {
    new CheckboxMenuItem("Guard"),
    new CheckboxMenuItem("Hide")
  };
  MenuItem[] file = {
    new MenuItem("Open"),
    new MenuItem("Exit")
  };
  // A second menu bar to swap to:
  MenuBar mb2 = new MenuBar();
  Menu fooBar = new Menu("fooBar");
  MenuItem[] other = {
    new MenuItem("Foo"),
    new MenuItem("Bar"),
    new MenuItem("Baz"),
  };
  Button b = new Button("Swap Menus");
  public Menu1() {
    for(int i = 0; i < flavors.length; i++) {
      m.add(new MenuItem(flavors[i]));
      // Add separators at intervals:
      if((i+1) % 3 == 0) 
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++)
      s.add(safety[i]);
    f.add(s);
    for(int i = 0; i < file.length; i++)
      f.add(file[i]);
    mb1.add(f);
    mb1.add(m);
    setMenuBar(mb1);
    t.setEditable(false);
    add("Center", t);
    // Set up the system for swapping menus:
    add("North", b);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b)) {
      MenuBar m = getMenuBar();
      if(m == mb1) setMenuBar(mb2);
      else if (m == mb2) setMenuBar(mb1);
    } 
    else if(evt.target instanceof MenuItem) {
      if(arg.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i < flavors.length; i++)
          if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      }
      else if(evt.target.equals(file[1]))
        System.exit(0);
      // CheckboxMenuItems cannot use String 
      // matching; you must match the target:
      else if(evt.target.equals(safety[0]))
        t.setText("Guard the Ice Cream! " +
          "Guarding is " + safety[0].getState());
      else if(evt.target.equals(safety[1]))
        t.setText("Hide the Ice Cream! " +
          "Is it cold? " + safety[1].getState());
      else 
        t.setText(arg.toString());
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Menu1 f = new Menu1();
    f.resize(300,200);
    f.show();
  }
} ///:~ 

In this program I avoided the typical long lists of add( ) calls for each menu because that seemed like a lot of unnecessary typing. Instead, I placed the menu items into arrays and then simply stepped through each array calling add( ) in a for loop. This makes adding or subtracting a menu item less tedious.

As an alternative approach (which I find less desirable since it requires more typing), the CheckboxMenuItems are created in an array of handles called safety; this is true for the arrays file and other as well.

This program creates not one but two MenuBars to demonstrate that menu bars can be actively swapped while the program is running. You can see how a MenuBar is made up of Menus, and each Menu is made up of MenuItems, CheckboxMenuItems, or even other Menus (which produce submenus). When a MenuBar is assembled it can be installed into the current program with the setMenuBar( ) method. Note that when the button is pressed, it checks to see which menu is currently installed using getMenuBar( ), then puts the other menu bar in its place.

When testing for “Open,” notice that spelling and capitalization are critical, but Java signals no error if there is no match with “Open.” This kind of string comparison is a clear source of programming errors.

The checking and un-checking of the menu items is taken care of automatically, but dealing with CheckboxMenuItems can be a bit surprising since for some reason they don’t allow string matching. (Although string matching isn’t a good approach, this seems inconsistent.) So you can match only the target object and not its label. As shown, the getState( ) method can be used to reveal the state. You can also change the state of a CheckboxMenuItem with setState( ).

You might think that one menu could reasonably reside on more than one menu bar. This does seem to make sense because all you’re passing to the MenuBar add( ) method is a handle. However, if you try this, the behavior will be strange and not what you expect. (It’s difficult to know if this is a bug or if they intended it to work this way.)

This example also shows what you need to do to create an application instead of an applet. (Again, because an application can support menus and an applet cannot directly have a menu.) Instead of inheriting from Applet, you inherit from Frame. Instead of init( ) to set things up, you make a constructor for your class. Finally, you create a main( ) and in that you build an object of your new type, resize it, and then call show( ). It’s different from an applet in only a few small places, but it’s now a standalone windowed application and you’ve got menus.

Dialog boxes

A dialog box is a window that pops up out of another window. Its purpose is to deal with some specific issue without cluttering the original window with those details. Dialog boxes are heavily used in windowed programming environments, but as mentioned previously, rarely used in applets.

To create a dialog box, you inherit from Dialog, which is just another kind of Window, like a Frame. Unlike a Frame, a Dialog cannot have a menu bar or change the cursor, but other than that they’re quite similar. A dialog has a layout manager (which defaults to BorderLayout) and you override action( ) etc., or handleEvent( ) to deal with events. One significant difference you’ll want to note in handleEvent( ): when the WINDOW_DESTROY event occurs, you don’t want to shut down the application! Instead, you release the resources used by the dialog’s window by calling dispose( ).

In the following example, the dialog box is made up of a grid (using GridLayout) of a special kind of button that is defined here as class ToeButton. This button draws a frame around itself and, depending on its state, a blank, an “x,” or an “o” in the middle. It starts out blank, and then depending on whose turn it is, changes to an “x” or an “o.” However, it will also flip back and forth between “x” and “o” when you click on the button. (This makes the tic-tac-toe concept only slightly more annoying than it already is.) In addition, the dialog box can be set up for any number of rows and columns by changing numbers in the main application window.

//: ToeTest.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;

class ToeButton extends Canvas {
  int state = ToeDialog.BLANK;
  ToeDialog parent;
  ToeButton(ToeDialog parent) {
    this.parent = parent;
  }
  public void paint(Graphics  g) {
    int x1 = 0;
    int y1 = 0;
    int x2 = size().width - 1;
    int y2 = size().height - 1;
    g.drawRect(x1, y1, x2, y2);
    x1 = x2/4;
    y1 = y2/4;
    int wide = x2/2;
    int high = y2/2;
    if(state == ToeDialog.XX) {
      g.drawLine(x1, y1, x1 + wide, y1 + high);
      g.drawLine(x1, y1 + high, x1 + wide, y1);
    }
    if(state == ToeDialog.OO) {
      g.drawOval(x1, y1, x1+wide/2, y1+high/2);
    }
  }
  public boolean 
  mouseDown(Event evt, int x, int y) {
    if(state == ToeDialog.BLANK) {
      state = parent.turn;
      parent.turn= (parent.turn == ToeDialog.XX ?
        ToeDialog.OO : ToeDialog.XX);
    } 
    else
      state = (state == ToeDialog.XX ? 
        ToeDialog.OO : ToeDialog.XX);
    repaint();
    return true;
  }
}

class ToeDialog extends Dialog {
  // w = number of cells wide
  // h = number of cells high
  static final int BLANK = 0;
  static final int XX = 1;
  static final int OO = 2;
  int turn = XX; // Start with x's turn
  public ToeDialog(Frame parent, int w, int h) {
    super(parent, "The game itself", false);
    setLayout(new GridLayout(w, h));
    for(int i = 0; i < w * h; i++)
      add(new ToeButton(this));
    resize(w * 50, h * 50);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      dispose();
    else 
      return super.handleEvent(evt);
    return true;
  }
}

public class ToeTest extends Frame {
  TextField rows = new TextField("3");
  TextField cols = new TextField("3");
  public ToeTest() {
    setTitle("Toe Test");
    Panel p = new Panel();
    p.setLayout(new GridLayout(2,2));
    p.add(new Label("Rows", Label.CENTER));
    p.add(rows);
    p.add(new Label("Columns", Label.CENTER));
    p.add(cols);
    add("North", p);
    add("South", new Button("go"));
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(arg.equals("go")) {
      Dialog d = new ToeDialog(
        this, 
        Integer.parseInt(rows.getText()),
        Integer.parseInt(cols.getText()));
      d.show();
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Frame f = new ToeTest();
    f.resize(200,100);
    f.show();
  }
} ///:~ 

The ToeButton class keeps a handle to its parent, which must be of type ToeDialog. As before, this introduces high coupling because a ToeButton can be used only with a ToeDialog, but it solves a number of problems, and in truth it doesn’t seem like such a bad solution because there’s no other kind of dialog that’s keeping track of whose turn it is. Of course, you can take another approach, which is to make ToeDialog.turn a static member of ToeButton. This eliminates the coupling, but prevents you from having more than one ToeDialog at a time. (More than one that works properly, anyway.)

The paint( ) method is concerned with the graphics: drawing the square around the button and drawing the “x” or the “o.” This is full of tedious calculations, but it’s straightforward.

A mouse click is captured by the overridden mouseDown( ) method, which first checks to see if the button has anything written on it. If not, the parent window is queried to find out whose turn it is and that is used to establish the state of the button. Note that the button then reaches back into the parent and changes the turn. If the button is already displaying an “x” or an “o” then that is flopped. You can see in these calculations the convenient use of the ternary if-else described in Chapter 3. After a button state change, the button is repainted.

The constructor for ToeDialog is quite simple: it adds into a GridLayout as many buttons as you request, then resizes it for 50 pixels on a side for each button. (If you don’t resize a Window, it won’t show up!) Note that handleEvent( ) just calls dispose( ) for a WINDOW_DESTROY so the whole application doesn’t go away.

ToeTest sets up the whole application by creating the TextFields (for inputting the rows and columns of the button grid) and the “go” button. You’ll see in action( ) that this program uses the less-desirable “string match” technique for detecting the button press (make sure you get spelling and capitalization right!). When the button is pressed, the data in the TextFields must be fetched, and, since they are in String form, turned into ints using the static Integer.parseInt( ) method. Once the Dialog is created, the show( ) method must be called to display and activate it.

You’ll notice that the ToeDialog object is assigned to a Dialog handle d. This is an example of upcasting, although it really doesn’t make much difference here since all that’s happening is the show( ) method is called. However, if you wanted to call some method that existed only in ToeDialog you would want to assign to a ToeDialog handle and not lose the information in an upcast.

File dialogs

Some operating systems have a number of special built-in dialog boxes to handle the selection of things such as fonts, colors, printers, and the like. Virtually all graphical operating systems support the opening and saving of files, however, and so Java’s FileDialog encapsulates these for easy use. This, of course, makes no sense at all to use from an applet since an applet can neither read nor write files on the local disk. (This will change for trusted applets in newer browsers.)

The following application exercises the two forms of file dialogs, one for opening and one for saving. Most of the code should by now be familiar, and all the interesting activities happen in action( ) for the two different button clicks:

//: FileDialogTest.java
// Demonstration of File dialog boxes
import java.awt.*;

public class FileDialogTest extends Frame {
  TextField filename = new TextField();
  TextField directory = new TextField();
  Button open = new Button("Open");
  Button save = new Button("Save");
  public FileDialogTest() {
    setTitle("File Dialog Test");
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    p.add(open);
    p.add(save);
    add("South", p);
    directory.setEditable(false);
    filename.setEditable(false);
    p = new Panel();
    p.setLayout(new GridLayout(2,1));
    p.add(filename);
    p.add(directory);
    add("North", p);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(open)) {
      // Two arguments, defaults to open file:
      FileDialog d = new FileDialog(this,
        "What file do you want to open?");
      d.setFile("*.java"); // Filename filter
      d.setDirectory("."); // Current directory
      d.show();
      String openFile;
      if((openFile = d.getFile()) != null) {
        filename.setText(openFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    } 
    else if(evt.target.equals(save)) {
      FileDialog d = new FileDialog(this,
        "What file do you want to save?",
        FileDialog.SAVE);
      d.setFile("*.java");
      d.setDirectory(".");
      d.show();
      String saveFile;
      if((saveFile = d.getFile()) != null) {
        filename.setText(saveFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Frame f = new FileDialogTest();
    f.resize(250,110);
    f.show();
  }
} ///:~ 

For an “open file” dialog, you use the constructor that takes two arguments; the first is the parent window handle and the second is the title for the title bar of the FileDialog. The method setFile( ) provides an initial file name – presumably the native OS supports wildcards, so in this example all the .java files will initially be displayed. The setDirectory( ) method chooses the directory where the file selection will begin. (In general, the OS allows the user to change directories.)

The show( ) command doesn’t return until the dialog is closed. The FileDialog object still exists, so you can read data from it. If you call getFile( ) and it returns null it means the user canceled out of the dialog. Both the file name and the results of getDirectory( ) are displayed in the TextFields.

The button for saving works the same way, except that it uses a different constructor for the FileDialog. This constructor takes three arguments and the third argument must be either FileDialog.SAVE or FileDialog.OPEN.

Contents | Prev | Next