Controller Components

Advanced Programming/Practicum
15-200


Introduction In this lecture we will examine the most important aspects of a variety of simple Java classes that act as controllers, extending the Component class: JButton, JTextField, JTextArea (and JScrollPane), JCheckBox, JRadioButton, and JSlider. We have already learned about some view-related aspects of these components: how they appear in the view of a GUI, meaning how we can specify their appearance (e.g, color) and layout. Now we will learn about some controller-related aspects of these components: how the user can act upon them to control the state in the model of the GUI.

In the case of the JRadioButton and JSlider components, we will examine two extensions (subclasses) of the JPanel class that I have written: RadioButtonPanel and PercentSliderPanel. Each generalizes and simplifies the creation and use of these built-in components; the code in each also illustrates a typical use of that component.

The code for creating and managing all these components (and specifying how they interact with the model) is typically defined in a Controller class. This is not because it MUST be defined there, but because it is REASONABLE to separate all the control information from the View class. In this case, two smaller classes are better than one bigger one. Typically, the build method in the View class will call methods in the Controller class to create components that it will position in the view; inside the methods written in Controller class is code that will instruct these components as to how to interact with the Model class.

We will also discuss the concept of a control pushing and pulling information, and see in general how to accomplish each with various control components. Pushing is when the control calls the model to tell it about an action/value the user has performed. Pulling is when the model calls the control asking it for its current value. Some controls naturally push information, other naturally have information pulled from them; some naturally allow both.

Finally, there is a Controllers Demonstrations folder that contains various project folders that illustrate concretely the code described in this lecture. You can easily change the code each contains to experiment with the controllers. Code shown there can be cut and pasted into other programs that use similar controller components. Please download, unzip, and examine its project folders.


Listeners and their Interfaces, Parameters, and Adapters The most important operation that we can perform on a component is to add a listener object to it. Whenever the user acts on a component, Java generates an event for it to handle. Java then uses the listener object for that component to handle the event.

For example, in the case of clicking the mouse while it is positioned over some button in the view, Java generates an event representing the action of pressing that button. Java then uses the listener added to that button to handle that event. If no listener has been added, pressing the button has no effect. To make this concrete now, here is some code that contains all the needed Java elements; we will discuss each in more detail soon.

  JButton doIt = new JButton("Do It");

  doIt.addActionListener(
    new ActionListener() {
      public void actionPerformed(ActionEvent e)
      {model.doIt();}
    }
  );
Each listener object is constructed from a class that implements an interface that specifies one or more methods: one method for each major kind of event that the listener can handle. For example, the ActionListener interface specifies just one void method to handle "action events", named actionPerformed. The addActionListener method allows us to add to a JButton any object implementing this interface (in fact, we can add more than one if neccessary, and each in turn will get to handle the event).

When Java senses an "action event" (that the user has pressed a button), Java processes the event by calling the actionPerformed method in any listener objects that were added to the component that the user acted on. Typically, the method in the listener object will itself call a method in the model to update the model's state.

Thus, a listener object is an indirect connection from the view to the model It acts as a bridge between the user (acting on the component) and the model: Java calls a method in the listener object, and that method calls some method in the model to change its state accordingly.

There is no simpler way in Java to create such a connection. Most interfaces (all the ones that specify more than one method) also have matching adapter classes in the standard Java library, which implement stubs for all the required methods in the interface. We will also use listeners to sense a mouse's movement and process other mouse events (e.g., clicking at locations which do not contain buttons).

To make this concrete now, here is some code that uses a MouseAdapter instead of the MouseListener interface (that it implements) to specify just one aspect of the mouse: what to do on a click. The MouseListener interface actually specifies five methods, which are all stubbed in the adapter.

  view.addMouseListener(
    new MouseAdapter () {
      public void mouseClicked(MouseEvent e)
      {model.clicked(e.getX(),e.getY());}
    }
  );
Here we use the getX and getY methods on the MouseEvent parameter that Java automatically supplies to the mouseClicked method. For your information, the other, stubbed methods in this adapter are mouseEntered, mouseExited, mousePressed, and mouseReleased.

So, Java watches for events on a component,and when it detects such an event, it executes the appropriate code for that event. We often write such code to push information to the model (by calling one of its methods, possibly supplying it with relevant parameters). A listener is typically defined by an interface that specifies the methods that it contains (the different events that it must handle). Java allows us to add a variety of listeners to components, most using the anonymous class syntax that we have discussed for use with interfaces and adapters.

  • addActionListeners can be called on JButton, JTextField, JCheckBox, and JRadioButton objects; its one associated method is specified by the ActionListener interface. It has no adapter because it defines just one method: actionPerformed, which has an ActionEvent parameter that supplies more information. A typical event is pressing a button or pressing Enter when in a text field.

  • addFocusListener can be called on JTextField objects; its two assocated methods (focusLoss and focusGained) are specified by the FocusListener interface, and implemented by the FocusAdapter class. Each method has a FocusEvent parameter that supplies more information. A typical event is a text field losing the focus when another control is clicked after the user has typed text in the text field.

  • addChangeListener can be called on JSlider objects; its one associated method is specified by the ChangeListener interface. It has no adapter because it defines just one method: stateChanged, which has a ChangedEvent parameter that supplies more information A typical event is moving the slider.

  • addMouseListener can be called on any Component object (at any granularity: an entire JFrame, one of its JPanels, etc); its five associated methods are specified by the MouseListener interface, and implemented by the MouseAdapter class. Each method has a MouseEvent parameter that supplies more information. A typical event is clicking in a JPanel or having the mouse enter/exit one.

  • addMouseMotionListener can be called on any Component object (at any granularity: an entire JFrame, one of its JPanels, etc); its two assocated methods (mouseMoved and mouseDragged) are specified by the MouseMotionListener interface, and implemented by the MouseMotionAdapter class. Each method has a MouseEvent parameter that supplies more information. A typical event is moving a mouse in a JPanel (with or without the mouse depressed).
Another way to organize these classes is:
  • Interfaces: ActionListener, FocusListener, ChangeListener, MouseListener, and MouseMotionListener; each of these has its own add... method: e.g., addActionListener.
  • Adpaters: FocusAdapter, MouseAdapter, and MouseMotionAdapter.
  • Parameters: ActionEvent, FocusEvent, ChangeEvent, and MouseEvent.
Please examine these interfaces, parameters, and adapters in more detail by reading their Javadoc. Most of these listener interfaces, parameters, and adapters must be imported from the java.awt.event package. In the sections below we will examine controller components in more detail, and how to use listeners with them.

Finally, we will eventually see how to use an ActionListener object to add a special action to another action listener (which is used in the case of controller components that both push and pull values).


JButton A JButton object is a complete controller-component. We can use it to illustrate the general form in which most controllers are created, and to which listeners are added.

Typically, code in the View class will construct a controller implicitly, by calling a "get" method in the Controller class. In the main GUI that I wrote for this lecture, the code in the View class looks like

  all.add(controller.getButton("B1"));
which adds to the JPanel named all (in the appropriate spot) the button returned by calling controller.getButton (labeled with B1). If we needed to modify the appearance of the JButton, we might instead write something like
  JButton temp = controller.getButton("B1");
  temp.setBackground(Color.green);
  ... call other methods on temp to change its visual appearance
  all.add(temp);
Note that all method calls that change its visual characteristics should appear in the View class,not the Controller class: this is a convention, not a Java requirement.

The code for getButton in the sample Controller class looks like

  JButton getButton(String label)
  {
    final JButton b = new JButton(label);  //final is important
	  
    b.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e)
        {
          System.out.println("Debug-Controller: " +
                             "Button pressed: " + b.getText() );
          //System.out.println("  Action-Event = " + e);
          model.buttonPress(b.getText());
        }});
	  
    return b;
  }
First this method constructs a JButton using the parameter label to label it; then it stores a reference to it into b (to which an action listener is added, and which is ultimately returned). This local variable must be declared final to be used inside the actionPerformed method in the anonymous class implementing the ActionListener interface: otherwise the compiler will detect this error and report it in a clear manner.

In between construction and return, the method adds an action listener to b. It uses the special syntax to create an object from an anonymous class implementing the ActionListener interface; this is similar to creating objects from an anonymous class which extends some adapter; Java allows this syntax to be used for both purposes. In the case of using the name of an interface, the body in brackets must define ALL the methods specified in the interface; in an adapter, only the ones overriding the stubbed methods must be written.

The ActionListener interface defines just one method: actionPerformed, which will be called whenever we press the button (that is the action/event that it is listening for). The implementation here, of this method, is to print a debugging message on the console (another debugging print is commented out; you can uncomment it to see what information an ActionEvent stores, when it is converted to a String and printed), and finally to push information to the model by calling its buttonPress method (supplying the button's label as a parameter). Note that the getText method on a JButton returns the String that is the label of that button.

Of course, the anonymous class can also declare and manipulate instance variables. We can define a special button whose label changes from one String to the next, each time it is pressed (and back to the first eventually).

 JButton getSwitchingButton(final String[] labels)
 {
   final JButton b = new JButton(labels[0]);
	  
   b.addActionListener(
     new ActionListener() {

       private int currentIndex = 0;
    
       public void actionPerformed(ActionEvent e)
       {
         model.buttonPress(b.getText());
         currentIndex = (currentIndex+1)%labels.length;
         b.setText(labels[currentIndex]);
      }});
	  
    return b;
  }
We might call such a method to create this special kind of button as
  controller.getSwitchingButton(
    new String[]{"first", "second", "third})
causing the buttons label to switch from first to second to third and back to first as it is pressed. Each time it is pressed the buttonPress method in the model is called, with its current label (so the model knows what to do).

What is interesting about this controller-component is that the class that implements ActionListener not only defines an actionPerformed method, but it also declares an instance variable (currentIndex), which is initialized and used subsequently in the in the actionPerformed method: each time the button is pressed, the index is label incremented (modularly). Because the parameter, labels is used inside the anonymous class, it too must be declared final.

This example begins to illustrate the power of using more complicated classes that implement the ActionListener interface. For example, we could use the actionPerformed method to access/update any instance variables stored in the Controller class (since the anonymous classes are inner classes). In this way, the buttons could communicate with each other: for example, pressing one button could allow it to change the labels on many other buttons. Also, the buttons could store information in the Controller class that is pulled by the Model class: by calling some method in the Controller class to retrieve their values.

The explanation for why we must use final is both simple yet difficult to understand. As we have seen in inner classes (and the anonymous class is an inner class) they can refer to all the variables specified in any outer methods and outer classes. That is why we can refer to the JButton b and the parameter String[] labels inside the methods in the inner classes. If these names were instance variables in the Controller class, there would be no problem referring to them (as is the case in some of the controllers below). But because these names refer to local variables and method parameters, which disappear when the method returns, Java requires that they be declared final. Then, Java stores the values in these variables/parameters (either primitive, or references to the objects) in some special instance variables that it creates solely for the anonymous class. Because the outer variables are declared final, we know that they can never have their values changed; thus, the specially created/initialized instance variables will always store the "right" values (whether primitive or reference to an object). Understanding this paragraph is difficult, as the idea is subtle; but it illustrate behavior consistent with what we know about Java.

Finally, we can call doClick on any button, which is the program's way of clicking the button. We can also call setActionCommand on a button (using a String parameter); doing so allows us to associate the String with the button. By this mechanism, we can associate different Strings with two buttons that happen to have the same label. We can call getActionCommand on the ActionEvent parameter supplied to the actionPerformed method.


JTextField A JTextField object is another complete controller component. We will see that, like a JButton, its is constructed, instrumented with listeners, and returned. The code for getJTextField in the sample Controller class looks like

  JTextField getTextField(final String id,
                          String initialContents,
                          int displaySize)
  {
    final JTextField tf = new JTextField(initialContents,displaySize);
	  
    tf.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e)
        {
          System.out.println("Debug-Controller: " + 
                             "Text field activated: " +
	                      tf.getText());
          //System.out.println("  Action-Event = " + e);
          model.textFieldEntered(id,tf.getText());
      }});
	  
    tf.addFocusListener(
      new FocusAdapter() {
        public void focusLost(FocusEvent e)
        {
	  System.out.println("Debug-Controller: " + 
                             "Text field loses focus: " +
	                     tf.getText());
	  //System.out.println("  Focus-Event = " + e);
          model.textFieldEntered(id,tf.getText());
      }});
  
    return tf;
  }

The implementation of the actionPerformed method (called when the user pressed "enter" while typing information into the text field), is to print a debugging message on the console and to push information to the model by calling its textFieldEntered method (supplying the id and content of the text field). The id can help the model understand what to do with the content. Note that the getText method on a JTextField returns the String that is typed in the text field. If it is supposed to be an int value, either the control or model can try to convert its value (by calling Integer.parseInt); whichever is more easily able to handle a NumberFormatException.

Likewise a focus listener is added to this controller-component. It calls exactly the same model method in the focusLost method. So, the textFieldEntered method in the model doesn't know whether it was called because the user pressed "enter" or because the user moved the cursor outside the text field: in each case it processes the text that the user has typed (and, it often makes no difference). The focusLost method is defined in the anonymous sublcass extending FocusAdapter; it is just one of two methods that deal with the foucus -the only one relevant to this application.


JCheckBox and JRadioButton A JCheckBox object is a complete controller component, and can be treated similarly to a JButton. It is often the case in GUIs that a series of check boxes are collected together. A JRadioButton is similar to a JCheckBox, but it has one special wrinkle: whereas any number of boxes can be checked in a series of check boxes, typically only one of a series or radio buttons should be selected at any time. When a new button is selected, whichever button is currently selected becomes deselected.

We accomplish this behavior by constructing a ButtonGroup object (from the javax.swing package) and adding to it, any number of JRadioButtons (or JCheckBoxes, for that matter). Then, whenever on button is selected, any currently selected button will first be deselected. This behavior is illustrated in a section below. See the RadioButtons class which I have written and uses a ButtonGroup to implement this "only one selected at a time" behavior.

A ButtonGroup has no visual appearance; it exists only as a collection of components with a logical purpose. It works by adding its own action listener to each component in the collection. When a component is selected, the actionPerformed it defines iterates through all the other components in the collection and ensures that they are not selected.


JTextArea A JTextArea object is a complete controller component, although it is most frequently embedded in a JScrollPane as illustrated below. We can treat it similarly to a JTextField, regarding both its view-related aspects (e.g., setEditable) and its controller-related aspects (e.g., adding listeners). Unlike a JTextField which is always one line (scrollable horizontally if it is too big to display), a JTextArea contains multiple lines.

In its simplest input/output uses, we can let the user type information into a JTextArea and later retrieve that input, or we can append text into this area (include "\n" to genrate new lines) which is displayed for output. We can do both, but that gets tricky and requires reading more information in this component's Javadoc.

We can create a scrollable text area as follows.

  JTextArea output = new JTextArea(5,30); //5 rows, 30 columns
  viewPanel.add(new JScrollPane(output));
Then, for example, in a Model-View-Controller, the view's update method could ask the model for information to display and append it to output (allowing the user to see new messages and scroll back to see old ones).
  void update()
  {output.append(model.getResponse()+"\n");}
The sample Controller class defines such a scrollable text area.

JPanel of Radio Buttons A series of JRadioButton objects are typically collected together and processed as a group, to form a single controller component. To make such a grouping easier to put in a GUI, I have written a simple RadioButtonPanel class to construct and process a group of radio buttons; the body of this class also shows how to use JRadioButtons. The class has only two constructors (we will use only the simpler one), a getSelectedValue method, and a selectedValue instance variable. For ease of use, I have made RadioButtonPanel a subclass of JPanel.

The header of the constructor to this class is defined as

   public RadioButtonPanel(String[] labels, final ActionListener action)
It specifies a first parameter that is a String array (where each entry is the label of a radio button), and an ActionListener that specifies the actionPerformed method to execute when each radio button is pressed; passing null to the second parameter means to perform no special action when a radio button is selected.

Recall that we can easily specify a String array filled with literals using Java. An example is new String[]{"A","B","C"}, which returns a reference to the String[] object pictured below

  The sample Controller class defines the following method to construct and return a radio button controller component.
  RadioButtonPanel getDecisionPanel()
  {
    return decisionPanel = 
      new RadioButtonPanel(
        new String[]{"Yes", "No", "Maybe So"},
        new ActionListener() {
          public void actionPerformed(ActionEvent e)
          {model.decisionPanelUpdate(decisionPanel.getSelectedValue());}
        });
  };
This method constructs a RadioButtonPanel, stores a reference to it in the instance variable decisionPanel (defined in the Controller class), and returns that reference (making use of the full semantics of the = operator). The panel will contain three radio buttons, with the labels Yes, No, and Maybe So. When pressed, each radio button will perform the action specified: calling the decisionPanelUpdate method in the model, pasing to it the currently selected radio button value (the label of the selected radio button) in the decisionPanel instance variable.

To accomplish all this the body of the constructor for the RadionButtonPanel is defined as

  ...
  ActionListener updateSelectedValue = new ActionListener() {
    public void actionPerformed(ActionEvent e)
    {
      System.out.println("Debug-Controller: " +
                         "RadioButtonPanel: " +
                         e.getActionCommand());
      //System.out.println("  Action-Event = " + e);
      selectedValue = e.getActionCommand();
      if (action != null)
        action.actionPerformed(e);
    }};
     
  ButtonGroup exclusive = new ButtonGroup();
  for (int i=0; i<labels.length; i++) {
    JRadioButton temp = new JRadioButton(labels[i]);
    add(temp);
    exclusive.add(temp);
    temp.addActionListener(updateSelectedValue); 
  }
  ...
This code first stores a reference to an object from an anonymous class that implements ActionListener. This is the action listner that will be executed by each radio button. Notice that it performs two main actions: first it uses e.getActionCommand() to return, and store into an instance variable, the label of the selected radio button; then it calls the actionPerformed method in the action listener passed to the parameter of this method, if non-null (which we saw above called the decisionPanelUpdate method in the model).

Once this action listener is constructed, we construct a ButtonGroup to ensure that only one of the radion buttons in this group shows as selected at any given time. Then, for each label in the String[], we

  • Create a radio button with its name
  • Add it to this JPanel (so it will be visible)
  • Add it to the button group, so only one can be selected at a time
  • Set its action listener to the one specified above
This is some very interesting programming: constructing an action listener that does some of its own work, and then calls another action listener (passed as a parameter); then creating and grouping a bunch of radio buttons and using this listener for all of them!

Although this class is sophisticated on the inside, it is pretty easy to use (at least for an intermediate Java programmer) from the outside. Also note that the class defines a getSelectedValue method, so that the model can access the selected radio button from this panel any time it needs it. The model stores a reference to the controller, and can refer to the package-friendly instance variable decisionPanel. Thus, the model can call: controller.decsisionPanel.getSelectedValue() to retrieve the label of the current the pressed radio button (returnining null if none has been pressed; another way to indicate such a problem is to throw an exception).

So, this controller-component supplies easy to use push AND pull behavior. By specifying an ActionListener in the constructor, we can have any selection push information to the model. By calling the getSelectedValue method for this panel, the model can pull whatever value it needs from this panel.


JPanel of JSlider Although a JSlider object can be used as a complete controller component (albeit one with many complicated aspects), I have written a simple SliderPanel class to construct and process a special kind of JSlider (and a label for it). This class has only a constructor, a getSelectedValue method, and a selectedValue instance variable.

In many ways this class is similar to the RadioButtonPanel class in how it works and how it is used by programmers, but it is more complicated internally. It is beyond the scope of this course to explain this class in detail, but I welcome students looking at my code and reading the Javadoc for the JSlider and HashTable classes to understand what I am doing here.


Mouse Listeners for JPanels The getMouseListener method in the sample Controller class returns a mouse listener that can be attached to any component (often it is attached to a JFrame or JPanel object (by the View class). Mouse listeners can handle the following events: a mouse is pressed, a mouse is released, a mouse is clicked (a press/release pair happening within some time limit), a mouse has moved over some component, and a mouse has moved to not be over some component).

The one I've written extends the MouseAdapter by defining methods to execute when the mouse is clicked, and when the mouse enters/exits the JPanel to which this object is added as a listener. In the former case, the mouseClicked method calls the mouseClick method in the model, passing it the coordinates of the click and a click count (for double clicking events), which it gets from the MouseEvent parameter. In the latter case, the controller itself contains code that changes the shape of the cursor.

The MouseMotionListener includes two more methods/events it can handle: the mouse being moved and the mouse being dragged. The mouse project folder in the Controllers Demonstrations folder contains methods defined (just to print information) for all of these events.


Keyboard Listeners for JFrames The getKeyListener method in the sample Controller class returns a key listener that can be attached to a JFrame object (by the View class). The one I've written extends the KeyAdapter by defining a method to execute when a key is typed (meaning pressed and then released: it combines these into one event rather than treating them separately). The keyTyped method calls the keyTyped method in the Model, passing it the character that was typed, which it gets from the KeyEvent parameter.

Note that this method works for Java 1.3 only. Java 1.4 requires a different way to attatch a key listener to a JFrame (and I haven't debugged how to do it yet).


The Controller Testbed In the grabbag project folder, the Application class, as always, is very simple. It
  • constructs an object from the Model, View, and Controller classes (the order should be unimportant)
  • adds references into each object to refer to the others (as necessary; at most six method calls, adding to all three objects references to the other two)
  • calls view.build(); to build the view, which constructs the controller components (and sets them up to push information to the model); if necessary, this method would also call some initialization method on the Model class.
  • calls view.show(); to paint the JFrame on the screen.

If we look in the View class, we will find that it creates a JPanel whose layout manager is a vertical box, to which controller-components (gotten from the Controller class) are added. In the case of the mouse and key listeners, these controllers are added directly to this JPanel and the JFrame that is the View respectively.

If we look in the Model class, we will see that all but the last method just print their name and their parameters; these methods are called by the controller-components, which push information to them. By keeping the view and model trivial, we can focus on the controller.

In contrast, the pullPressed method (which is called by the bottom controller-component) illustrates how the model can pull information from the controller. It displays the values currently stored in two radio button panels and a slider. To do so, it refers to package-friendly instance variables in the Controller class, calling their getSelectedValue methods. Technically, one could pull the value in a text field too (via getText), but I did not write the code to do so; I could have stored this information in package-friendly instance variables too, or in a large map from each text field (or some String label) to its contents.

Finally, the Controller class is filled with methods that construct controller components, set them up to push information to the model, and return them for placement in the view. Thus, the controller class provides the stated bridge between the view and model.

We will briefly go over in class the code for the controller-components discused above, and see how they are created in the View class and push information to the Model class; we will also see how we can make changes to and generally experiment with the components supplied. There is a Controllers Demonstrations that we can use to illustrate all these concepts with a variety of controllers. We can also easily change the code it contains to experiment with other controllers. Code here can be cut and pasted into other programs that use similar controller components. Please download, unzip, and examine this file. The GUI it creates looks like


Problem Set To ensure that you understand all the material in this lecture, please solve the the announced problems after you read the lecture.

If you get stumped on any problem, go back and read the relevant part of the lecture. If you still have questions, please get help from the Instructor, a CA, or any other student.

  1. Change the text field component so that the model could pull information from it.