JMU
Worker and Timer Threads
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
Using a SwingWorker
  1. Construct a descendant of SwingWorker
  2. Call its execute() method
  3. doInBackground() is called
  4. If desired, listen for PropertyChangeEvents
  5. done() is called
Progress
Cancellation
SwingWorker Threads
An Example

A Calculator

javaexamples/gui/PiCalculator.java
import java.util.*;
import javax.swing.*;

/**
 * Compute pi using the van Wijngaarden transformation
 * of the Gregory-Leibniz series
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class PiCalculator extends SwingWorker<Double,Double>
{
// SwingWorker<T,V> where:
//     T is the result type returned by doInBackground() and get()
//     V is the type used for intermediate results by publish() and process()
//
    private int         digits;    
    
    /**
     * Explicit Value Constructor
     *
     * @param digits   The number of digits of accuracy
     */
    public PiCalculator(int digits)
    {
       this.digits   = digits;
    }

    /**
     * Calculate the i,j term in the transformation
     */
    private double pi(int i, int j)
    {
       if (isCancelled()) return 4.0;
       
       
       double     den, num, result;
       
       if (j == 0) return 0.0;

       // We could call setProgress() and pass an int in [0,100]
       // to cause PropertyChangeEvent objects to be fired
       // to listeners (in the Event Dispatch Thread).
       //
       // We could also call publish() to cause this object's
       // process() method to be called in the Event DispatchThread.
       

       if (i == 0)
       {
          // pi(0,1): 4/1   pi(0,2): 4/1 - 4/3   pi(0,3): 4/1-4/3+4/5  ...
          result = 0.0;          
          for (int k=1; k<=j; k++)
          {
             num = Math.pow(-1.0, k+1) * 4.0;
             den = (k * 2.0) - 1;
             result +=  num / den;
          }
       }
       else
       {
          result = (pi(i-1,j) + pi(i-1,j+1))/2.0;
       }
       
       return result;
    }
    
    /**
     * The code to execute in the background thread
     */
    public Double doInBackground()
    {
       double result;

       result = pi(digits+1, digits+1);

       // This will cause the result to be retrievable
       // using the get() method
       return Double.valueOf(result);
    }
}
        
An Example (cont.)

Using the Calculator

javaexamples/gui/PiDriver.java
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;

/**
 * An example that uses PiCalculator (which is a the SwingWorker)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class PiDriver implements ActionListener, PropertyChangeListener
{
    private int            digits;
    private JButton        cancelButton, startButton;
    private JTextField     digitsField, resultArea;    
    private PiCalculator   task;
    

    /**
     * The entry point
     */
    public static void main(String[] args)
    {
       new PiDriver();
    }

    /**
     * Default Constructor
     */
    public PiDriver()
    {
       Container   contentPane;       
       JFrame      frame;
       JPanel      north;
       

       frame = new JFrame();
       frame.setTitle("Calculate pi");
       contentPane = frame.getContentPane();
       contentPane.setLayout(new BorderLayout());
       
       resultArea = new JTextField();
       contentPane.add(resultArea, BorderLayout.SOUTH);
       
       north = new JPanel();
       north.add(new JLabel("Digits:"));
       digitsField = new JTextField(10);
       north.add(digitsField);
       
       startButton = new JButton("Start");       
       startButton.addActionListener(this);       
       startButton.setEnabled(true);       
       north.add(startButton);
       cancelButton = new JButton("Cancel");
       cancelButton.addActionListener(this);       
       cancelButton.setEnabled(false);       
       north.add(cancelButton);

       contentPane.add(north, BorderLayout.NORTH);
       
       frame.setSize(400,100);
       frame.setVisible(true);

       task = null;
       
    }

    /**
     * Handle actionPerformed() messages (required by
     * ActionListener)
     *
     * @param evt  The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent evt)
    {
       String       ac;
       
       ac = evt.getActionCommand();
       if (ac.equals("Start") && (task == null))
       {
          try
          {
             resultArea.setText("");             
             digits = Integer.parseInt(digitsField.getText());
             task = new PiCalculator(digits);
             task.addPropertyChangeListener(this);             
             startButton.setEnabled(false);
             cancelButton.setEnabled(true);
             task.execute();
          }
          catch (NumberFormatException nfe)
          {
             nfe.printStackTrace();
             
          }
       }
       else if (ac.equals("Cancel"))
       {
          if (task != null) 
          {
             startButton.setEnabled(true);
             cancelButton.setEnabled(false);
             task.cancel(true);
             task = null;
          }
       }
    }
             
    /**
     * Handle propertyChange() messages (required by
     * PropertyChangeListener)
     *
     * @param evt  The PropertyChangeEvent that generated the message
     */
    public  void propertyChange(PropertyChangeEvent evt) 
    {
       String    format;

       if (evt.getPropertyName().equals("progress")) 
       {
          // Could do something like:
          // progressBar.setValue((Integer)evt.getNewValue());
       }
       else if (evt.getPropertyName().equals("state")) 
       {
          if (evt.getNewValue().equals(SwingWorker.StateValue.DONE)) 
          {
             startButton.setEnabled(true);
             cancelButton.setEnabled(false);
       
             format = "%"+(digits+1)+"."+digits+"f\n";
       
             try
             {
                resultArea.setText(String.format(format, task.get()));
             }
             catch (Exception e)
             {
             }

             task = null;
          }
       }
    }
}
        
Timed Events
An Example

A BlinkingLabel

javaexamples/gui/BlinkingLabel.java
package gui;

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

/**
 * A Jlabel that blinks (in an annoying way)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class BlinkingLabel extends JLabel implements ActionListener
{
    private boolean           status;    
    private Color             textColor;    
    private Timer             timer;
    

    /**
     * Explicit Value Constructor
     */
    public BlinkingLabel(String text)
    {
       super(text);
       performCommonConstructionTasks();
    }

    /**
     * Explicit Value Constructor
     */
    public BlinkingLabel(String text, Icon icon, int horizontalAlignment)
    {
       super(text, icon, horizontalAlignment);
       performCommonConstructionTasks();
    }

    /**
     * Explicit Value Constructor
     */
    public BlinkingLabel(String text, int horizontalAlignment)
    {
       super(text, horizontalAlignment);
       performCommonConstructionTasks();
    }

    /**
     * Handle actionPerformed() message (required by ActionListener)
     *
     * @param evt  The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent evt)
    {
       if (status) setForeground(textColor);
       else        setForeground(Color.RED);
       
       status = !status;
    }

    /**
     * Perform common construction tasks
     */
    private void performCommonConstructionTasks()
    {
       timer = new Timer(500, this);
       status = false;
    }
    
    /**
     * Start the blinking
     */
    public void start()
    {
       textColor = getForeground();
       timer.restart();
    }
    
    /**
     * Stop the blinking
     */
    public void stop()
    {
       timer.stop();
       setForeground(textColor);
    }
    
    
}