CSC224 May08

slide version

single file version

Contents

  1. Graphics Applications
  2. The model: 1
  3. The model: 2
  4. The view: 1
  5. The view: 2
  6. The controller: 1
  7. The controller: 2
  8. The controller: 3
  9. The MouseListener controller
  10. Implementing the MouseListener: 1
  11. Implementing the MouseListener: 2
  12. Implementing the MouseListener: 3
  13. Implementing MouseListener: 4
  14. Implementing the MouseListener: 5
  15. Controllers and Components
  16. Inheritance from JFrame
  17. The new RectangleComponentFrame class
  18. Revised RectangleComponentViewer application class
  19. Timer Events
  20. ActionListener interface
  21. ActionListener Example
  22. Summary

Graphics Applications[1] [top]

A pattern for developing flexible classes for graphics applications involves 3 parts:

These 3 parts are often referred to as the model-view-controller pattern.

The model: 1[2] [top]

The RectangleComponent object contains the data - the model.

    1	
    2	public class RectangleComponent extends JComponent
    3	{  
    4	   private static final int BOX_X = 100;
    5	   private static final int BOX_Y = 100;
    6	   private static final int BOX_WIDTH = 20;
    7	   private static final int BOX_HEIGHT = 30;
    8	
    9	   private Rectangle box;
   10	
   11	   public RectangleComponent()
   12	   {  
   13	      // The rectangle that the paintComponent method draws
   14	      box = new Rectangle(BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT);         
   15	   }
   16	
   17	   public void paintComponent(Graphics g)
   18	   {  
   19	      Graphics2D g2 = (Graphics2D) g;
   20	
   21	      g2.draw(box);
   22	   }
   23	
   24	   /**
   25	      Moves the rectangle to the given location.
   26	      @param x the x-position of the new location
   27	      @param y the y-position of the new location
   28	   */
   29	   public void moveTo(int x, int y)
   30	   {
   31	      box.setLocation(x, y);
   32	      repaint();      
   33	   }
   34	} 

The model: 2[3] [top]

The RectangleComponent inherits the ability to display the rectangle from the JComponent class.

However, an application must still create the view by adding this RectangleComponent to a frame and making the frame visible.

The view: 1[4] [top]

    1	
    2	public class RectangleComponentViewer
    3	{  
    4	   private static final int FRAME_WIDTH = 300;
    5	   private static final int FRAME_HEIGHT = 400;
    6	
    7	   public static void main(String[] args)
    8	   {        
    9	    final RectangleComponent component = new RectangleComponent();
   10	
   ...        ...
   31	
   32         JFrame frame = new JFrame();
   33	      frame.add(component);
   34	
   35	      frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
   36	      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   37	      frame.setVisible(true);
   38	   }
   39	} 

The view: 2[5] [top]

The RectangleComponentViewer

This view class does rely on the components it adds to the view to be able to display themselves.

The paintComponent of each component added to a JFrame executes whenever the JFrame is made visible.

The controller: 1[6] [top]

    1	
    2	public class RectangleComponentViewer
    3	{  
    4	   private static final int FRAME_WIDTH = 300;
    5	   private static final int FRAME_HEIGHT = 400;
    6	
    7	   public static void main(String[] args)
    8	   {        
    9	      final RectangleComponent component = new RectangleComponent();
   10	
   11	      // Add mouse press listener         
   12	
   13	      class MousePressListener implements MouseListener
   14	      {  
   15	         public void mousePressed(MouseEvent event)
   16	         {  
   17	            int x = event.getX();
   18	            int y = event.getY();
   19	            component.moveTo(x, y);
   20	         }
   21	
   22	         // Do-nothing methods
   23	         public void mouseReleased(MouseEvent event) {}
   24	         public void mouseClicked(MouseEvent event) {}
   25	         public void mouseEntered(MouseEvent event) {}
   26	         public void mouseExited(MouseEvent event) {}
   27	      }
   28	         
   29	      MouseListener listener = new MousePressListener();
   30	      component.addMouseListener(listener);
   31	
   32	      JFrame frame = new JFrame();
   33	      frame.add(component);
              ...
   38	   }
   39	} 

The controller: 2[7] [top]

The controller manages the user interaction with the view.

If the view consist of multiple components, the controller may also consist of individual controllers - one for each component.

Controller code executes in response to events, e.g. user interaction with the view such as clicking on a canvas or a button, typing in a textbox component.

Controller code to handle an event is called an event handler.

Java controller's are typically created as instances of classes called listeners.

A JComponent maintains a list of EventListeners. So creating components that inherit from JComponent means that we can easily add controllers (i.e. listeners) to such components using the inherited methods for adding listeners.

The controller: 3[8] [top]

Lines 13 - 27 of the RectangleComponentViewer class create a listener to control mouse press events on the RectangleComponent.

Lines 29 - 30 create a listener of this class and add this listener to the RectangleComponent.

So the model (RectangleComponent) is primarily the data, but through inheritance from JComponent it can be connected to views and controllers:

The MouseListener controller[9] [top]

Each method gets a parameter of type MouseEvent passed to it automatically when the corresponding event occurs.

The MouseEvent object has both public methods and some public data members. The x and y coordinates of the mouse position are public data members.

Implementing the MouseListener: 1[10] [top]

   11	      // Add mouse press listener         
   12	
   13	      class MousePressListener implements MouseListener
   14	      {  
   15	         public void mousePressed(MouseEvent event)
   16	         {  
   17	            int x = event.getX();
   18	            int y = event.getY();
   19	            component.moveTo(x, y);
   20	         }
   21	
   22	         // Do-nothing methods
   23	         public void mouseReleased(MouseEvent event) {}
   24	         public void mouseClicked(MouseEvent event) {}
   25	         public void mouseEntered(MouseEvent event) {}
   26	         public void mouseExited(MouseEvent event) {}
   27	      }
      
    

Note: Inner classes defined in a method can only access local variables of the method if they are declared with the final qualifier!

So component is defined in main:

    ...
    7	   public static void main(String[] args)
    8	   {        
    9	      final RectangleComponent component = new RectangleComponent();
    ...  
    

Implementing the MouseListener: 2[11] [top]

The MouseListener interface has 5 methods to implement.

For the mouse controller used in this program, only one of the methods does anything - mousePressed.

Instead of writing the 4 other methods with empty bodies, there is a class that already does that for all 5 methods - MouseAdapter.

So our mouse controller class can just inherit from MouseAdapter and then override the 1 method mousePressed.

Implementing the MouseListener: 3[12] [top]

Here is the revised MousePressedListener class using inheritance from the MouseAdapter class:


 class MousePressListener extends MouseAdapter
 {  
    public void mousePressed(MouseEvent event)
    {  
       int x = event.getX();
       int y = event.getY();
       component.moveTo(x, y);
    }
    // Other methods in the MouseListener class are inherited
    // from MouseAdapter (they do nothing)
 }

Implementing MouseListener: 4[13] [top]

If only one instance of a class is to be created and that instance is just passed as a parameter to another method, it seems extravagant to create a named class.

Java allows creating anonymous classes (unnamed classes) from interfaces or from parent classes.

Here is how we could create an object that implements the MouseListener interface without creating a named class:


    MouseListener listener = 
      new MouseListener()
      {
         public void mousePressed(MouseEvent event)
         {  
            int x = event.getX();
            int y = event.getY();
            component.moveTo(x, y);
         }
         // Do-nothing methods
         public void mouseReleased(MouseEvent event) {}
         public void mouseClicked(MouseEvent event) {}
         public void mouseEntered(MouseEvent event) {}
         public void mouseExited(MouseEvent event) {}
    
      });
    component.addMouseListener(listener);

Alternatively, we could replace the interface name MouseListener above by the class name MouseAdapter and omit the Do-nothing methods.

Implementing the MouseListener: 5[14] [top]

We can even omit the listener variable and insert the code that uses the anonymous class directly as the parameter:

   component.addMouseListener(
     new MouseAdapter()
     {
         public void mousePressed(MouseEvent event)
         {  
            int x = event.getX();
            int y = event.getY();
            component.moveTo(x, y);
         }
     });
    

Controllers and Components[15] [top]

Since individual controllers are usually added to components, the controller code must have access to a reference to the component it will control.

There are several possibilities. The example has this organization:

This approach can make the main method become quite bloated.

Instead of having the main method of the application create the frame AND create the components AND add listeners to the components, we can use inheritance to create a subclass of JFrame that handles these responsibilities.

Then the main application can just create an instance of this specialized frame and make it visible.

Inheritance from JFrame[16] [top]

We can revise the classes from the Rectangle example to use a specialized frame class:

Previous classes:
The revised classes:

The model, RectangleComponent, is not changed in the revised version.

Most of the responsibilities that were in the main method of the old RectangleComponentViewer have been assumed by the new RectangleComponentFrame.

The new RectangleComponentFrame class[17] [top]


public class RectangleComponentFrame extends JFrame
{
  private RectangleComponent component;

  public RectangleComponentFrame(int w, int h)
  {
    // Initialize JFrame parent and set the frame title
    super("Rectangle Mover"); 
    
    // Create a rectangle component 
    component = new RectangleComponent();

    // Create and add a mouse listener for the component
    component.addMouseListener(
      new MouseAdapter()
      {
         public void mousePressed(MouseEvent event)
         {  
            int x = event.getX();
            int y = event.getY();
            component.moveTo(x, y);
         }
      });

    // Add the component to this frame
    add(component);

    // Set the size of this frame
    setSize(w,h); 
  }    
}

Revised RectangleComponentViewer application class[18] [top]


public class RectangleComponentViewer
{  
   private static final int FRAME_WIDTH = 300;
   private static final int FRAME_HEIGHT = 400;

   public static void main(String[] args)
   {        
      JFrame frame = new RectangleComponentFrame(FRAME_WIDTH, FRAME_HEIGHT);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
   }
} 

Timer Events[19] [top]

Many events generated by a graphical application are events are caused by a user such as mouse events or typing in a textbox, etc.

In contrast, a program can generate regular timer events by creating a Timer instance.

 ActionListener listener = ...

 final int DELAY = 100; // Milliseconds between timer ticks
 Timer t = new Timer(DELAY, listener);
 t.start();      

    

ActionListener interface[20] [top]

The timer event handler must implement this interface:

 public interface ActionListener
 {
   public void actionPerformed(ActionEvent event);
 }      
    

ActionListener Example[21] [top]

The Timer and TimerListener classes can be used to cause the Rectangle component to move at each timer event:


 class TimerListener implements ActionListener
 {
    public void actionPerformed(ActionEvent event)
    {
       component.moveBy(1, 1);
    }
 }

Summary[22] [top]