Mismatch between javax.swing.ProgressMonitor and SwingWorker.setProgress

84 views Asked by At

Class javax.swing.ProgressMonitor provides method setMaximum(int max) which is documented as

Specifies the maximum value.

However, SwingWorker<Void, Void>.setProgress(int progres) is documented as updating the progress bound property, which must be between 0 and 100. If you update it with a value outside that range it throws an exception. ProgressMonitor.setMaximum(int max) makes no mention of the SwingWorker progress property limitation.

I have an application that reads from a document file, and I report progress each page. The number of pages might be anywhere from 1 to 150. Setting the bound property max works while the number of pages is less than 100, but of course fails when there are more. I've wrapped myself around a couple of axles trying to resolve this in an elegant fashion, and have now given up. I'll go back to my code and rewrite my progress reporting to calculate a percentage.

The tutorials for using ProgressMonitor don't mention this; they all just report progress between 1 and 100 without mentioning why ProgressMonitor.setProgress(), when used with SwingWorker, has this limitation.

Is there something I'm missing here?

EDIT: I forgot to mention that it's particularly annoying because I want to update the message in the dialog to say "reading page X of Y", and I evidently cannot do that with the bound property if I'm ever going to have a document of over 100 pages. So I have to update that message outside the property change logic.

EDIT 2: The following is the example program given for Java's tutorial "How to use Progress Bars", in the "How to use Progress Monitors" section. The original does not have a field for the end value of progress, but instead uses a hard-coded 100. I've altered it to take a value from the user for the end value of the progress bar. It works the same as the original for a value of 100, sort-of works for values between 10 and 100, and throws a (runtime) exception for a value over 100.

My 'rant', I suppose, is that the two setProgress methods -- ProgressMonitor and SwingWorker -- work differently. SwingWorker.setProgress() cannot be used for values over 100, so their example of how to use ProgressMonitor.setProgress() is limited by that, though the tutorial doesn't mention it.

My question was "am I missing anything". I guess not.

package sandbox.progressmonitor;

/*
 * Copyright (c) 1995 - 2008 Sun Microsystems, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ProgressMonitor;
import javax.swing.SwingWorker;

public class ProgressMonitorDemo extends JPanel implements ActionListener, PropertyChangeListener
{
  private static final long serialVersionUID = 1L;
  private ProgressMonitor progressMonitor;
  private JButton         startButton;
  private JTextField      endValue;             // GUI value for end of progress
  private int             endValueInt;          // int value for end of progress
  private JTextArea       taskOutput;
  private Task            task;

  class Task extends SwingWorker<Void, Void>
  {
    @Override
    public Void doInBackground()
    {
      Random random = new Random();
      int progress = 0;
      setProgress(0);
      try
      {
        Thread.sleep(1000);
        while (progress < endValueInt && !isCancelled())
        {
          // Sleep for up to one second.
          Thread.sleep(random.nextInt(1000));
          // Make random progress.
          progress += random.nextInt(10);
          System.out.printf("Setting progress to %d%n", progress);
          setProgress(Math.min(progress, endValueInt));   // <-- looks reasonable, but a non-obvious bug
                                                          // if endValueInt is over 100, this throws an exception
        }
      } 
      catch (Throwable doNotIgnore)
      {
        doNotIgnore.printStackTrace();                    // <-- demonstrates RuntimeException thrown
      }
      return null;
    }

    @Override
    public void done()
    {
      Toolkit.getDefaultToolkit().beep();
      startButton.setEnabled(true);
      progressMonitor.setProgress(0);
    }
  }

  public ProgressMonitorDemo()
  {
    super(new BorderLayout());

    // Create the demo's UI.
    startButton = new JButton("Start");
    startButton.setActionCommand("start");
    startButton.addActionListener(this);
    
    endValue = new JTextField(5);             // <-- new field
    
    JPanel topPanel = new JPanel();           // <-- new panel for field and button
    topPanel.add(endValue);                   // <--
    topPanel.add(startButton);                // <--

    taskOutput = new JTextArea(5, 20);
    taskOutput.setMargin(new Insets(5, 5, 5, 5));
    taskOutput.setEditable(false);

    add(topPanel, BorderLayout.PAGE_START);
    add(new JScrollPane(taskOutput), BorderLayout.CENTER);
    setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

  }

  /**
   * Invoked when the user presses the start button.
   */
  public void actionPerformed(ActionEvent evt)
  {
    endValueInt = 0;
    try
    {
      endValueInt = Integer.parseInt(endValue.getText());
    }
    catch (Exception e) { /* do nothing, value of 0 will cause abort */ }
    
    if (endValueInt <=10 || endValueInt > 5000)
    {
      JOptionPane.showMessageDialog(this, "Enter an ending value between 10 and 5000");
    }
    else
    {
      // <-- attempt at monitoring value that might be over 100
      progressMonitor = new ProgressMonitor(ProgressMonitorDemo.this, "Running a Long Task", "", 0, endValueInt);
      progressMonitor.setProgress(0);
      progressMonitor.setMillisToDecideToPopup(10);
      progressMonitor.setMillisToPopup(10);
      task = new Task();
      task.addPropertyChangeListener(this);
      task.execute();
      startButton.setEnabled(false);
    }
  }

  /**
   * Invoked when task's progress property changes.
   */
  public void propertyChange(PropertyChangeEvent evt)
  {
    if ("progress" == evt.getPropertyName())
    {
      int progress = (Integer) evt.getNewValue();
      progressMonitor.setProgress(progress);                          // <-- THIS setProgress(int) will take a value > 100;
                                                                      // but SwingWorker.setProgress(int) will not
      // since the progress property will not take a value > 100,
      // we can't use property change for monitoring things that have
      // a high value > 100.
      String message = String.format("Completed %d%%.\n", progress);
      progressMonitor.setNote(message);
      taskOutput.append(message);
      if (progressMonitor.isCanceled() || task.isDone())
      {
        Toolkit.getDefaultToolkit().beep();
        if (progressMonitor.isCanceled())
        {
          task.cancel(true);
          taskOutput.append("Task canceled.\n");
        }
        else
        {
          taskOutput.append("Task completed.\n");
        }
        startButton.setEnabled(true);
      }
    }

  }

  /**
   * Create the GUI and show it. For thread safety, this method should be
   * invoked from the event-dispatching thread.
   */
  private static void createAndShowGUI()
  {
    // Create and set up the window.
    JFrame frame = new JFrame("ProgressMonitorDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // Create and set up the content pane.
    JComponent newContentPane = new ProgressMonitorDemo();
    newContentPane.setOpaque(true); // content panes must be opaque
    frame.setContentPane(newContentPane);

    // Display the window.
    frame.pack();
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread:
    // creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        createAndShowGUI();
      }
    });
  }
}
0

There are 0 answers