Simple Java I/OTopGUI components

GUI components

Java has GUI components like those in Grace, though associating actions with components requires a few more steps.

Creating and adding GUI components to the screen

To install and use a GUI component you must:

  1. Declare a variable of the appropriate type and initialize it with the component.
  2. Install it somewhere in the window using a layout manager
  3. If you want your program to react to a use of the component, then you must associate a "listener" with the GUI component and write an appropriate method to perform the action.

Adding graphic user interface (GUI) items to the screen is pretty easy. Here are the steps:

  1. Create the item and initialize it if necessary. E.g.,
          figureMenu = new JComboBox<String>();
          figureMenu.addItem("FramedSquare");
          figureMenu.addItem("FramedCircle");
          figureMenu.addItem("FilledSquare");
    
  2. Add the items to the window, and validate the pane. E.g.,
          add(figureMenu, BorderLayout.SOUTH);
          add(colorMenu, BorderLayout.NORTH);
          validate();
    

Let's look at a simplified drawing program to see a few of those steps. DrawingProgram.

First we declare the ComboBox (pop-up menu):

   // The menu determining which kind of object is drawn
   private JComboBox<String> figureMenu;  

Then initialize it in the begin method:

   public void begin() {
      // Create menu for selecting figures
      figureMenu = new JComboBox<String>();
      figureMenu.addItem("FramedSquare");
      figureMenu.addItem("FramedCircle");
      figureMenu.addItem("FilledSquare");

      add(figureMenu, BorderLayout.SOUTH);
      validate();
      ...
   }

The first statement creates the menu. The next three add entries to the menu. The add statement adds the figureMenu to the bottom of the screen. The last statement tells Java to arrange all the pieces nicely in the window.

Let's talk briefly about layout managers. The main window generated by WindowController uses BorderLayout. A window with BorderLayout has 5 slots that can hold items. They are the NORTH, SOUTH, EAST, WEST, and CENTER. There are public constants from the BorderLayout class referring to each of those. You can refer to them by prefixing the name with BorderLayout, e.g., BorderLayout.SOUTH.

The add method when you have border layout takes two parameters, one with the component, while the second has where to put the item. (See the code above.) When your class extends WindowController the CENTER portion of the screen will be filled with the drawing canvas.

Each of the slots in the window can hold only one item. Thus if you put the figureMenu is the south slot, then other GUI items must be put elsewhere. Luckily, Java has containers that can hold multiple items and they can be put in one of those slots. The containers in Java are generated by class JPanel. Adding a bit to the complexity, JPanels use a different layout manager than our WindowController. It uses FlowLayout rather than BorderLayout. When you add items to a container using FlowLayout it simply adds them to the container from left to right and centers them. Thus, to add an item to a JPanel, we use an add method that only needs a single parameter, the item to be added to the panel.

The first drawing program that we described when explaining arrays used a JPanel to place three combo boxes in the south part of the window.

   // menus for shape, color, and command
   private JComboBox<String> shapeChoice;
   private JComboBox<String> colorChoice;
   private JComboBox<String> commandChoice;

The begin method then created the menus, put them in the JPanel, and then added the JPanel to the window in the south:

   public void begin() {
      // create panel to hold choice buttons
      JPanel menuPanel = new JPanel();
   
      // menu for selecting or adding
      commandChoice = new JComboBox<String>();
      commandChoice.addItem("Add new item");
      commandChoice.addItem ("Recolor item");
      commandChoice.addItem("Move item");
      commandChoice.addItem("Delete item");
      menuPanel.add(commandChoice);     // Note only a single parameter -- no direction!
   
      // Set up menu for shapes
      shapeChoice = new JComboBox<String>();
      shapeChoice.addItem("Circle");
      shapeChoice.addItem("Square");
      menuPanel.add(shapeChoice);         // Note only a single parameter -- no direction!
   
      // Set up menu for colors
      colorChoice = new JComboBox<String>();
      colorChoice.addItem("Red");
      colorChoice.addItem("Green");
      colorChoice.addItem("Blue");
      menuPanel.add(colorChoice);        // Note only a single parameter -- no direction!
   
      // Add the panel to screen
      add(menuPanel, BorderLayout.SOUTH);  // Two parameters because adding to WindowController
      validate();
   }

Java supports more kinds of GUI components than just JComboBox. A relatively compact introduction to the most popular components and how to use them can be found at http://www.cs.pomona.edu/classes/cs051/handouts/SwingGUICheatSheet.html. These include JButton (corresponding to Button in Grace), JLabel (corresponding to TextBox in Grace), JSlider, JTextField (corresponding to TextField in Grace), and JTextArea.

We will use JButton in examples below. We construct a button via new JButton(s) where s is the label on the string. Methods getText and setText(newS) are available to retrieve and update the label.

Adding Listeners to GUI components

As in Grace, if we wish to have a GUI component react to a user selection then we must associate an action with it. Here is the handler for Grace that we wrote for a clear button:

    clearButton.onMousePressDo {mevt:MouseEvent ->
       canvas.clear
    }

We used a method like onMousePressDo to associate an action with the clearButton. That action took a MouseEvent parameter mevt and then cleared the canvas.

For pop-up menus (items of type Choice), we used the method onChangeDo:

 // when user select new color, change color of newShape
  colorMenu.onChangeDo{ evt: Event ->
    match(colorMenu.selected)
      case {"red" -> newShape.color := red}
      case {"blue" -> newShape.color := blue}
      case {"green" -> newShape.color := green}
      case {"yellow" -> newShape.color := yellow}
  }

Until very recently, Java did not allow us to pass blocks like these to methods, so it developed a different mechanism for associating actions with GUI components. It associated Listener objects with GUI components. These listener objects had methods that could respond to user actions. For example, JButton objects are prepared to add ActionListeners. The interface ActionListener has a single method with the following header:

   void actionPerformed (ActionEvent e)

If lisOb is an object of type ActionListener then we associate it with clearButton by writing clearButton.addActionListener(lisOb). Once this has been done, any time the user clicks on clearButton, the system will make a method request of actionPerformed on lisObj - resulting in execution of that object's code for the method.

Different GUI items have different kinds of listeners. For now we will only focus on responses to buttons, pop-up menus, and text fields, and they all are associated with the same kind of listener, ActionListener. Read the GUI cheat sheet documentation for more information on the different kinds of listeners associated with different GUI items. More information is available in the documentation for the standard Java libraries.

For simplicity here, we will always have the main program (the class extending WindowController) act as the listener for all components. Thus to set up the main program to handle events generated by the user pressing a button, we must do the following:

  1. Add this as a listener to the GUI item. E.g.,
        colorMenu.addActionListener(this);
    
  2. Add a declaration that the class implements the appropriate listener interface. E.g.,
        public class DrawingProgram extends WindowController 
                                    implements ActionListener { ... }
    
  3. Add the method promised by the listener interface:
        public void actionPerformed(ActionEvent event) {
           ...
        }
    

Let's see what this looks like in practice. We are going to write a Java program that is equivalent to our drawingProgram in Grace. The complete Java program can be found here

This program has two pop-up menus, only one of which, the color menu, responds to user selections. The other one is only consulted when the user clicks on the screen. The color menu is declared with the other instance variables at the top of the screen:

   // The menu determining which color the object should be
   private JComboBox<String> colorMenu;

The begin method contains the code to initialize it and put it on the screen

   public void begin() {
      ...
      // create menu for selecting colors
      colorMenu = new JComboBox<String>();
      colorMenu.addItem("Red");
      colorMenu.addItem("Blue");
      colorMenu.addItem("Green");
      colorMenu.addItem("Yellow");
      
      colorMenu.addActionListener(this);   // this object now notified of any selections on menu
       ...
      add(colorMenu, BorderLayout.NORTH);  // add menu to north side of window.
       ...

For it to be legal to use this as the listener, we need it to implement ActionListener. This requires us to implement the method actionPerformed, but also requires us to tell Java that we want this class to be an ActionListener:

public class DrawingProgram extends WindowController 
                            implements ActionListener {...}

Remember Java will not believe a DrawingProgram is of type ActionListener unless we tell it so in the class header.

In the following we have the implementation of method actionPerformed. Because of this code, when the user makes a selection from the color menu, the method will first make sure newShape (the last object put on the screen) is different from null (i.e., has a value associated with it) and then calls the private helper method setColorFromMenu to change the color of newShape.

   /**
    * Select a new color for the last object drawn
    * @param event - contains information on object generating the event
    */
   public void actionPerformed(ActionEvent event) {
      if (newShape != null) {
         setColorFromMenu();
      }
   }

   /**
    * Change the color of the newest shape on canvas to new value of
    * the color menu.
    */
   private void setColorFromMenu() {
      Object colorChoiceString = colorMenu.getSelectedItem();
      if (colorChoiceString.equals("Red")) {
         newShape.setColor(Color.RED);
      } else if (colorChoiceString.equals("Blue")) {
         newShape.setColor(Color.BLUE);
      } else if (colorChoiceString.equals("Green")) {
         newShape.setColor(Color.GREEN);
      } else if (colorChoiceString.equals("Yellow")) {
         newShape.setColor(Color.YELLOW);
      }
   }

We didn't use the event parameter to actionPerformed, but in general it can be used to determine, for example, the source of the event. This is necessary in Java because one listener can be listening to many different GUI components, so we sometimes need to figure out which one. See the description of ActionEvent in the Java documentation for a listing of all methods.

JTextBox objects also use ActionListeners.

Thus the main differences between handling user events on GUI items in Grace and Java are:

  1. It is a bit more complex to put an item in the window because the layout is more elaborate in Java. It also offers more fine control of the layout than Grace does.
  2. Rather than associating an action directly with a GUI item, Java associates a listener with the GUI item. That listener must then have a method of the right form (e.g., actionPerformed) to handle the event.
  3. Because Java has a nominal type system, you must declare in the header of the listener class that it implements the appropriate kind of listener, e.g. ActionListener. (In our case, we will always use the main program as the listener, so the class extending WindowController will declare that it implements the appropriate listeners.)

Simple Java I/OTopGUI components