JMU
Event-Driven Programs
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
Event-Driven Design
Event-Driven Design (cont.)

Using an Event Queue

images/event-queue.gif
Roles of the Event Queue
Variants

Event Listening

images/event-listening.gif

Event Bubbling

images/event-bubbling.gif
The Event Queue and Dispatch Thread in Java
GUIs in Java
Components in Swing
Components in Swing (cont.)

Using a JLabel

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: label0)
               label = new JLabel(s, SwingConstants.CENTER);
        
Components in Swing (cont.)

Using a JButton

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: button0)
               button = new JButton(CHANGE);
        
Containers in Swing
Containers in Swing (cont.)

Using a JFrame

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: jframe)
               window = new JFrame();
       window.setSize(600,400);
        
Containers in Swing (cont.)

Getting the Content Pane

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: contentPane0)
               contentPane = (JPanel)window.getContentPane();
        
Layout in Swing
Layout in Swing (cont.)

Using Absolute Layout

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: contentPane1)
               contentPane.setLayout(null);  
        
javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: label1)
               label.setBounds(50,50,500,100);       
       contentPane.add(label);    
        
javaexamples/app/BadInteractiveRandomMessageSwingApplication.java (Fragment: button1)
               button.setBounds(450,300,100,50);
       contentPane.add(button);
        
Java Programs
Java Applications
Java Applets
Simple GUI Applications

With a Problem

javaexamples/app/BadRandomMessageApplication.java
        import java.util.*;
import javax.swing.*;


/**
 * A simple example of a GUI application 
 *
 * Note: This example contains a common mistake made by beginning
 * programmers.  Specifically, it manipulates GUI elements
 * outside of the event dispatch thread
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class BadRandomMessageApplication
{
    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };
    
    
    /**
     * The entry point of the application
     *
     * @param args   The command-line arguments
     */
    public static void main(String[] args) throws Exception
    {
       JFrame                    window;       
       JLabel                    label;       
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Construct the "window"
       window = new JFrame();
       window.setSize(600,400);
       window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       
       
       // Get the container for all content
       contentPane = (JPanel)window.getContentPane();
       contentPane.setLayout(null);  
     
       // Add a component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    

       // Make the "window" visible
       window.setVisible(true);
    }
    
    
    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }
}
        
Simple GUI Applications (cont.)

Using the Event Dispatch Thread

javaexamples/app/BadRandomMessageSwingApplication.java
        import java.util.*;
import javax.swing.*;


/**
 * A simple example of a GUI application 
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      BadRandomMessageSwingApplication
       implements Runnable
{
    // Attributes
    private JLabel                label;       

    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };


    /**
     * The entry point of the application
     *
     * @param args   The command-line arguments
     */
    public static void main(String[] args) throws Exception
    {
       SwingUtilities.invokeAndWait(
                      new BadRandomMessageSwingApplication());
    }
    
    
    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }

    

    /**
     * The code to be executed in the event dispatch thread
     * (required by Runnable)
     */
    public void run()
    {
       JFrame                    window;     
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Construct the "window"
       window = new JFrame();
       window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);       
       window.setSize(600,400);
       
       // Get the container for all content
       contentPane = (JPanel)window.getContentPane();
       contentPane.setLayout(null);  
       
       // Add a component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    

       // Make the "window" visible
       window.setVisible(true);
    }

}
        
Simple GUI Applications (cont.)

Adding Event Handling

javaexamples/app/BadInteractiveRandomMessageSwingApplication.java
        import java.awt.event.*;
import java.util.*;
import javax.swing.*;


/**
 * A simple example of a GUI application that responds
 * to events (in this case, events generated by a button)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      BadInteractiveRandomMessageSwingApplication
       implements ActionListener, Runnable
{
    // Attributes
    private JLabel                label;       

    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // String "constants"
    private static final String   CHANGE = "Change";    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };


    /**
     * The entry point of the application
     *
     * @param args   The command-line arguments
     */
    public static void main(String[] args) throws Exception
    {
       SwingUtilities.invokeAndWait(
                  new BadInteractiveRandomMessageSwingApplication());
    }

    /**
     * Handle actionPerformed messages
     * (required by ActionListener)
     *
     * @param event   The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent event)
    {
       String      actionCommand;
       
       actionCommand = event.getActionCommand();
       if (actionCommand.equals(CHANGE))
       {
          label.setText(createRandomMessage());          
       }
    }

    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }
    
    /**
     * The code to be executed in the event dispatch thread
     * (required by Runnable)
     */
    public void run()
    {
       JButton                   button;       
       JFrame                    window;     
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Construct the "window"
       window = new JFrame();
       window.setSize(600,400);
       window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);       
       
       // Get the container for all content
       contentPane = (JPanel)window.getContentPane();
       contentPane.setLayout(null);  
       
       // Add the message component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    

       // Add the button to the container
       button = new JButton(CHANGE);
       button.setBounds(450,300,100,50);
       contentPane.add(button);
       button.addActionListener(this);       

       // Make the "window" visible
       window.setVisible(true);
    }
}
        
Simple GUI Applets

Like the Above Applications

javaexamples/app/BadRandomMessageJApplet.java
        import java.util.*;
import javax.swing.*;

/**
 * A simple example of a GUI JApplet
 *
 * Note: This example contains a subtle mistake.  Specifically, it
 * manipulates GUI elements in the init() method which is
 * NOT called in the event dispatch thread.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class   BadRandomMessageJApplet 
       extends JApplet
{
    // Attributes
    private JLabel                label;       

    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };


    /**
     * Default Constructor
     */
    public BadRandomMessageJApplet()
    {
       super();
    }


    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }
    

    /**
     * Called to indicate that this JApplet has been loaded
     */
    public void init()
    {
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Get the container for all content
       contentPane = (JPanel)getContentPane();
       contentPane.setLayout(null);     
  
       // Add a component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    
    }
}
        
javaexamples/app/BadInteractiveRandomMessageJApplet.java
        import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * A simple example of a GUI JApplet that responds
 * to events (in this case, events generated by a button)
 *
 * Note: This example contains a subtle mistake.  Specifically, it
 * manipulates GUI elements in the init() method which is
 * NOT called in the event dispatch thread.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      BadInteractiveRandomMessageJApplet 
       extends    JApplet
       implements ActionListener
{
    // Attributes
    private JLabel                label;       

    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // String "constants"
    private static final String   CHANGE = "Change";    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };

    /**
     * Default Constructor
     */
    public BadInteractiveRandomMessageJApplet()
    {
       super();
    }

    /**
     * Handle actionPerformed messages
     * (required by ActionListener)
     *
     * @param event   The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent event)
    {
       String      actionCommand;
       
       actionCommand = event.getActionCommand();
       if (actionCommand.equals(CHANGE))
       {
          label.setText(createRandomMessage());          
       }
    }
    
    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }
    
    /**
     * Called to indicate that this JApplet has been loaded
     */
    public void init()
    {
       JButton                   button;       
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Get the container for all content
       contentPane = (JPanel)getContentPane();
       contentPane.setLayout(null);     
  
       // Add a component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    

       // Add the button to the container
       button = new JButton(CHANGE);
       button.setBounds(450,300,100,50);
       button.addActionListener(this);       
       contentPane.add(button);
    }
}
        
Unifying Applications and Applets
Unifying Applications and Applets (cont.)

The JApplication Class

javaexamples/app/JApplication.java (Fragment: run)
            
    /**
     * The actual entry point into this JApplication
     * (required by Runnable)
     *
     * This method is called in the event dispatch thread.
     */
    public final void run()
    {
       constructMainWindow();       
       
       init();

       mainWindow.setVisible(true);       
    }
        
javaexamples/app/JApplication.java (Fragment: constructMainWindow2)
               mainWindow = new JFrame();       
       mainWindow.setTitle("James Madison University");
       mainWindow.setResizable(false);       

       contentPane = (JPanel)mainWindow.getContentPane();       
       contentPane.setLayout(null);       
       contentPane.setDoubleBuffered(false);       
        
javaexamples/app/JApplication.java (Fragment: init)
        
    /**
     * This method is called just before the main window
     * is first made visible
     */
    public abstract void init();
        
Unifying Applications and Applets (cont.)

An Example JApplication

javaexamples/app/BadRandomMessageJApplication.java
        import java.util.*;
import javax.swing.*;

import app.JApplication;



public class      BadRandomMessageJApplication
       extends    JApplication
{
    // Attributes
    private JLabel                    label;       

    // The pseudo-random number generator
    private static Random             rng = new Random();    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };


    /**
     * The entry point of the application
     *
     * @param args   The command-line arguments
     */
    public static void main(String[] args) throws Exception
    {
       SwingUtilities.invokeAndWait(
          new BadRandomMessageJApplication(600,400));
    }
    

    /**
     * Explicit Value Constructor
     */
    public BadRandomMessageJApplication(int width, int height)
    {
       super(width, height);       
    }
    

    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }
    
    
    /**
     * Called to indicate that this JApplication has been loaded
     */
    public void init()
    {
       JPanel                    contentPane;
       String                    s;
       

       // Select a message at random
       s = createRandomMessage();

       // Get the container for all content
       contentPane = (JPanel)getContentPane();
       contentPane.setLayout(null);
       
       // Add a component to the container
       label = new JLabel(s,SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    
    }
}
        
Unifying Applications and Applets (cont.)
Unifying Applications and Applets (cont.)

Correcting the Historical Problem

javaexamples/app/MultimediaRootPaneContainer.java
        package app;

import javax.swing.*;

/**
 * Requirements of a RootPaneContainer in a MultimediaApp
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface MultimediaRootPaneContainer 
       extends   RootPaneContainer
{
    
    /**
     * Returns the value of the "named" parameter
     *
     * @param name  The name/index of the parameter
     */
    public abstract String getParameter(String name);
    
}
        
Unifying Applications and Applets (cont.)

Correcting the Remaining Problems with the Decorator Pattern

images/multimedia-app_initial.gif
Unifying Applications and Applets (cont.)

The Interface

javaexamples/app/MultimediaApp.java
        package app;

import javax.swing.*;

/**
 * The requirements of a multimedia Applet/application
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface MultimediaApp
{

    /**
     * Called to indicate that this MultimediaApp should destroy any 
     * resources that it has allocated
     */
    public abstract void destroy();

    
    /**
     * Called to indicate that this MultimediaApp has been loaded
     */
    public abstract void init();
    

    /**
     * Set the MultimediaRootPaneContainer for the MultimediaApp
     *
     * In most cases, the MultimediaRootPaneContainer will be either
     * a MultimediaApplication or a MultimediaApplet
     *
     * @param container   The RootPaneContainer for this MultimediaApp
     */
    public abstract void setMultimediaRootPaneContainer(
                        MultimediaRootPaneContainer container);    


    /**
     * Called to indicate that this MultimediaApp has been started
     */
    public abstract void start();

    
    /**
     * Called to indicate that this MultimediaApp has been stopped
     */
    public abstract void stop();    
    
}
        
Unifying Applications and Applets (cont.)

The MultimediaApplet Class

javaexamples/app/MultimediaApplet.java
        package app;

import java.awt.*;
import javax.swing.*;

/**
 * A MultimediaApplet is a JApplet that delegates all calls
 * to "transition" methods to a MultimediaApp.  The calls
 * to the MultimediaApp object's "transition" methods will
 * be made in the event dispatch thread.
 *
 * Note: The browser/appletviewer is not supposed to call the
 * "transition" methods in the event dispatch thread.  This class
 * checks to make sure [since SwingUtilities.invokeAndWait() must not
 * be called in the event dispatch thread].  While there is a small
 * performance penalty incurred as a result, the "transition" methods
 * are not called frequently enough for this to matter.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class      MultimediaApplet 
                extends    JApplet
                implements MultimediaRootPaneContainer
{
    private MultimediaApp          app;
    
    /**
     * Explicit Value Constructor
     *
     * @param app   The MultimediaApp to delegate to
     */
    public MultimediaApplet(MultimediaApp app)
    {
       super();
       
       this.app = app;
       setLayout(null);       
       app.setMultimediaRootPaneContainer(this);       
    }


    /**
     * Called to indicate that this Applet should destroy any 
     * resources that it has allocated
     */
    public void destroy()
    {
       if (SwingUtilities.isEventDispatchThread()) app.destroy();          
       else
       {
          try {SwingUtilities.invokeAndWait(new DestroyRunnable());}
          catch (Exception e) {}
       }
    }


    /**
     * Get a reference to the MultimediaApp that is
     * being delegated to
     *
     * @return  The MultimediaApp
     */
    protected MultimediaApp getMultimediaApp()
    {
       return app;       
    }


    /**
     * Called to indicate that this Applet has been
     * loaded
     */
    public void init()
    {
       if (SwingUtilities.isEventDispatchThread()) app.init();          
       else
       {
          try {SwingUtilities.invokeAndWait(new InitRunnable());}
          catch (Exception e) {}
       }
    }


    /**
     * Called to indicate that this Applet has been
     * started
     */
    public void start()
    {
       if (SwingUtilities.isEventDispatchThread()) app.start();          
       else
       {
          try {SwingUtilities.invokeAndWait(new StartRunnable());}
          catch (Exception e) {}
       }
    }


    /**
     * Called to indicate that this Applet has been
     * stopped
     */
    public void stop()
    {
       if (SwingUtilities.isEventDispatchThread()) app.stop();          
       else
       {
          try {SwingUtilities.invokeAndWait(new StopRunnable());}
          catch (Exception e) {}
       }
    }
    

    // --Inner Classes--


    /**
     * A Runnable that delegates to the MultimediaApp object's
     * destroy() method
     */
    private class DestroyRunnable implements Runnable
    {
        public void run()
        {
           app.destroy();           
        }        
    }


    /**
     * A Runnable that delegates to the MultimediaApp object's
     * init() method
     */
    private class InitRunnable implements Runnable
    {
        public void run()
        {
           app.init();           
        }        
    }


    /**
     * A Runnable that delegates to the MultimediaApp object's
     * start() method
     */
    private class StartRunnable implements Runnable
    {
        public void run()
        {
           app.start();           
        }        
    }


    /**
     * A Runnable that delegates to the MultimediaApp object's
     * stop() method
     */
    private class StopRunnable implements Runnable
    {
        public void run()
        {
           app.stop();           
        }        
    }
    
    
}
        
Unifying Applications and Applets (cont.)

An Example App

javaexamples/app/RandomMessageApp.java
        package app;

import java.util.*;
import javax.swing.*;

/**
 * A simple example of a MultimediaApp
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class   RandomMessageApp
       extends AbstractMultimediaApp
{
    // Attributes
    private JLabel                label;       

    // The pseudo-random number generator
    private static Random         rng = new Random();    

    // The messages
    private static final String[] MESSAGES = 
    {
       "What a great example!",
       "This class is great.",
       "I can't wait to do the programming assignments.",
       "I wish lectures lasted for 5 hours.",
       "I've never had a better Professor."
    };

    

    /**
     * "Create" a message at random
     *
     * @return   The message
     */
    private static String createRandomMessage()
    {
       return  MESSAGES[rng.nextInt(MESSAGES.length)];
    }


    /**
     * Called to indicate that this MultimediaApp has been loaded
     */
    public void init()
    {
       JPanel                    contentPane;
       String                    s;
       
       // Select a message at random
       s = createRandomMessage();

       // Get the container for all content
       contentPane = (JPanel)rootPaneContainer.getContentPane();
       contentPane.setLayout(null);     
  

       // Add a component to the container
       label = new JLabel(s, SwingConstants.CENTER);
       label.setBounds(50,50,500,100);       
       contentPane.add(label);    
    }
}
        
Unifying Applications and Applets (cont.)

The Application and Applet

javaexamples/app/RandomMessageMultimediaApplet.java
        package app;


/**
 * A simple example of a MultimediaApplet
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class    RandomMessageMultimediaApplet 
       extends  MultimediaApplet
{

    /**
     * Default Constructor
     */
    public RandomMessageMultimediaApplet()
    {
       super(new RandomMessageApp());
    }
    

}
        
javaexamples/app/RandomMessageMultimediaApplication.java
        package app;

import java.util.*;
import javax.swing.*;


/**
 * A simple example of a MultimediaApplication
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      RandomMessageMultimediaApplication
       extends    MultimediaApplication
{

    /**
     * The entry-point of the application
     *
     * @param args    The command-line arguments
     */
    public static void main(String[] args) throws Exception
    {
       SwingUtilities.invokeAndWait(
          new RandomMessageMultimediaApplication(args, 600, 400));
    }
    

    /**
     * Explicit Value Constructor
     *
     * @param args    The command-line aguments
     * @param width   The width of the content (in pixels)
     * @param height  The height of the content (in pixels)
     */
    public RandomMessageMultimediaApplication(String[] args,
                                              int width, int height)
    {
       super(args, new RandomMessageApp(), width, height);     
    }
    
}
        
Unifying Applications and Applets (cont.)

Unifying Calls to the "Transition" Methods

javaexamples/app/JApplication.java (Fragment: constructMainWindow3)
               mainWindow.setDefaultCloseOperation(
                                 JFrame.DO_NOTHING_ON_CLOSE);      
       mainWindow.addWindowListener(this);
    }
        
javaexamples/app/JApplication.java (Fragment: windowOpened)
        
    /**
     * Handle windowOpened messages -- the first time the window is
     * made visible (required by WindowListener)
     *
     * @param event   The WindowEvent that generated the message
     */
    public void windowOpened(WindowEvent event)
    {
       resize();       
       start();       
    }
        
javaexamples/app/JApplication.java (Fragment: windowDeiconified)
        
    /**
     * Handle windowDeiconified messages -- when the window is maximized
     * (required by WindowListener)
     *
     * @param event   The WindowEvent that generated the message
     */
    public void windowDeiconified(WindowEvent event)
    {
       start();       
    }
        
javaexamples/app/JApplication.java (Fragment: windowIconified)
        
    /**
     * Handle windowIconified messages -- when the window is minimized
     * (required by WindowListener)
     *
     * @param event   The WindowEvent that generated the message
     */
    public void windowIconified(WindowEvent event)
    {
       stop();       
    }
        
javaexamples/app/JApplication.java (Fragment: windowClosing)
            
    /**
     * Handle windowClosing messages 
     * (required by WindowListener)
     *
     * @param event   The WindowEvent that generated the message
     */
    public void windowClosing(WindowEvent event)
    {
       exit();
    }
        
javaexamples/app/JApplication.java (Fragment: exit)
        
    /**
     * Exist this JApplication (after prompting the user
     * for a verification)
     */
    private void exit()
    {
       int        response;
       

       response = JOptionPane.showConfirmDialog(mainWindow,
                                      "Exit this application?",
                                      "Exit?", 
                                      JOptionPane.YES_NO_OPTION);

       if (response == JOptionPane.YES_OPTION)
       {
          mainWindow.setVisible(false);       
          stop();       
          mainWindow.dispose();          
       }
    }
        
javaexamples/app/JApplication.java (Fragment: windowClosed)
            
    /**
     * Handle windowClosed messages -- when the window is disposed
     * (required by WindowListener)
     *
     * @param event   The WindowEvent that generated the message
     */
    public void windowClosed(WindowEvent event)
    {
       destroy();       
       System.exit(0);       
    }
        
Unifying Applications and Applets (cont.)

The Final Design

images/multimedia-app.gif
Unifying Applications and Applets (cont.)

Loading Resources

javaexamples/io/ResourceFinder.java
        package io;

import java.io.*;
import java.net.*;
import java.util.*;


/**
 * A ResourceFinder is used to find a "resource" either in
 * the .jar file (containing the ResourceFinder class)
 * or in the local file system
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class ResourceFinder
{
    private static final ResourceFinder instance = 
                                         new ResourceFinder();    


    /**
     * Create an instance of a ResourceFinder
     *
     * @return   The instance
     */
    public static ResourceFinder createInstance()
    {
       return instance;       
    }


    /**
     * Find a resource
     *
     * @return  The InputStream of the resource (or null)
     */
    public InputStream findInputStream(String name)
    {
       Class          c;       
       InputStream    is;

       // Get the Class for this class
       c     = this.getClass(); 

       // Get a URL for the resource
       is    = c.getResourceAsStream(name);

       
       return is;       
    }


    /**
     * Find a resource
     *
     * @return  The URL of the resource (or null)
     */
    public URL findURL(String name)
    {
       Class          c;       
       URL            url;

       // Get the Class for this class
       c     = this.getClass(); 

       // Get a URL for the resource
       url   = c.getResource(name);

       
       return url;       
    }



    /**
     * Load a list of resource names from a list (e.g., file)
     *
     * Note: This method does not return an array of InputStream
     * objects to conserver resources.
     *
     * @param  listName  The name of the list
     * @return           The resource names
     */
    public String[] loadResourceNames(String listName)
    {
       ArrayList<String>        buffer;       
       BufferedReader           in;       
       InputStream              is;
       String                   line;       
       String[]                 names;
       
       names = null;       
       is    = findInputStream(listName);

       if (is != null)
       {
          try
          {
             in     = new BufferedReader(new InputStreamReader(is));
             buffer = new ArrayList<String>();
             
             while ((line=in.readLine()) != null)
             {
                buffer.add(line);
             }
             names = new String[buffer.size()];             
             buffer.toArray(names);             
          }
          catch (IOException ioe)
          {
             // Can't read the list
          }
       }
       return names;       
    }


    
}
        
Timed Events
Timed Events (cont.)

Design of a Simple Metronome

images/metronome.gif
Timed Events (cont.)
javaexamples/event/MetronomeListener.java
        package event;

/**
 * The requirements of an object that listens to a Metronome
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface MetronomeListener
{
    /**
     * Handle a Metronome tick
     *
     * @param millis   The number of milliseconds since the Metronome started
     */
    public abstract void handleTick(int millis);    
    
}
        
Timed Events (cont.)
javaexamples/event/Metronome.java (Fragment: listeners)
            /**
     * Add a MetronomeListener
     *
     * @param ml   The MetronomeListener to add
     */
    public synchronized void addListener(MetronomeListener ml)
    {
       listeners.add(ml);              
       copyListeners();
    }
    /**
     * Copy the collection of listeners
     *
     * The copy is used in the event dispatch thread to avoid
     * concurrent modification problems.
     *
     * This method is called infrequently (and, in general,
     * before events are dispatched) so it does not cause
     * any real efficiency problems.
     */
    private void copyListeners()
    {
       copy = new MetronomeListener[listeners.size()];
       listeners.toArray(copy);
    }
    /**
     * Remove a MetronomeListener
     *
     * @param ml   The MetronomeListener to remove
     */
    public synchronized void removeListener(MetronomeListener ml)
    {
       listeners.remove(ml);       
       copyListeners();
    }
        
javaexamples/event/Metronome.java (Fragment: notifyListeners)
            /**
     * Notify observers in the GUI/event-dispatch thread.
     *
     * Note: Listeners are notified in the REVERSE order  
     * in which they are added.
     */
    private synchronized void notifyListeners()
    {
       // Setup the state of the MetronomeTickDispatcher
       dispatcher.setup(copy, time);
       
       // Cause the run() method of the dispatcher to be
       // called in the GUI/event-dispatch thread
       EventQueue.invokeLater(dispatcher);
    }
        
javaexamples/event/Metronome.java (Fragment: MetronomeTickDispatcher)
            /**
     * A MetronomeTickDispatcher is used by a Metronome to
     * inform listeners of a tick.
     *
     * This class implements Runnable because its run()
     * method will be called in the GUI/event-dispatch thread.
     */
    private class MetronomeTickDispatcher implements Runnable
    {
        private MetronomeListener[]   listeners;
        private int                   time;
        
        
        /**
         * Code to be executed in the event-dispatch thread
         * (required by Runnable)
         *
         * Specifically, notify the listeners
         */
        public void run()
        {
           int    n;
           
           n = listeners.length;           
           for (int i=n-1; i>=0; i--)
           {
              if (listeners[i] != null) 
                 listeners[i].handleTick(time);
           }
        }

        
        /**
         * Setup this MetronomeTickDispatcher for the next
         * dispatch
         *
         * @param listeners  The collection of MetronomeListener objects
         * @param time       The (relative) time of the tick
         */
        public void setup(MetronomeListener[] listeners, 
                          int time)
        {
           this.listeners = listeners;           
           this.time      = time;           
        }
    }
        
javaexamples/event/Metronome.java (Fragment: start)
            /**
     * Start this Timer
     */
    public void start()
    {
       if (timerThread == null)
       {
          keepRunning = true;
          timerThread = new Thread(this);
          timerThread.start();
       }
    }
        
javaexamples/event/Metronome.java (Fragment: run)
            /**
     * The code that is executed in the timer thread
     * (required by Runnable)
     */
    public void run()
    {
       int       currentDelay;       
       long      currentTick, drift;       

       currentDelay = delay;       

       if (adjusting) lastTick = System.currentTimeMillis();

       while (keepRunning)
       {
          try
          {
             timerThread.sleep(currentDelay);

             time += currentDelay;              
             
             if (adjusting) 
             {
                // Compensate for any drift
                currentTick = System.currentTimeMillis();
                drift = (currentTick - lastTick) - currentDelay;       
                
                currentDelay = (int)Math.max(0, delay-drift);
                lastTick = currentTick;                
             }
             
             notifyListeners();
          }
          catch (InterruptedException ie)
          {
             // stop() was called
          }
       }
       timerThread = null;       
    }
        
Timed Events (cont.)

A Simple Example

javaexamples/app/StopWatchApp.java
        package app;


import java.util.*;
import java.awt.event.*;
import javax.swing.*;

import app.AbstractMultimediaApp;
import event.*;


/**
 * A simple MultimediaApp that uses a Metronome
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class      StopWatchApp
       extends    AbstractMultimediaApp
       implements ActionListener, MetronomeListener
{
    private boolean                   running;
    private JLabel                    label;       
    private Metronome                 metronome;
    
    private static final String       START = "Start";
    private static final String       STOP  = "Stop";


    /**
     * Handle actionPerformed messages
     * (required by ActionListener)
     *
     * @param event  The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent event)
    {
       String   actionCommand;
       
       actionCommand = event.getActionCommand();
       if      (actionCommand.equals(START))
       {
          label.setText("0");          
          metronome.reset();          
          metronome.start();
          running = true;          
       }
       else if (actionCommand.equals(STOP))
       {
          metronome.stop();
          running = false;          
       }
    }
    

    /**
     * Respond to handleTick messages
     * (required by MetronomeListener)
     *
     * @param millis   The time
     */
    public void handleTick(int millis)
    {
       label.setText(""+millis/1000);       
    }
    

    /**
     * Called to indicate that this MultimediaApp has been loaded
     */
    public void init()
    {
       JButton                   start, stop;       
       JPanel                    contentPane;       
              
       running = false;
       
       contentPane = (JPanel)rootPaneContainer.getContentPane();   
       contentPane.setLayout(null);
       
       label       = new JLabel("0");
       label.setBounds(250,100,100,100);       
       contentPane.add(label);    

       start = new JButton(START);
       start.setBounds(50,300,100,50);
       start.addActionListener(this);
       contentPane.add(start);
       

       stop  = new JButton(STOP);
       stop.setBounds(450,300,100,50);
       stop.addActionListener(this);
       contentPane.add(stop);
       

       metronome = new Metronome(1000, true);
       metronome.addListener(this);
    }
    

    /**
     * Called to indicate that this MultimediaApp should start
     * (required by MultimediaApp)
     */
    public void start()
    {
       if (running) metronome.start();
    }
    

    /**
     * Called to indicate that this MultimediaApp should stop
     * (required by MultimediaApp)
     */
    public void stop()
    {
       if (running) metronome.stop();
    }
}