Solved

Separating GUI code from processing code when using Progress Monitor or Bar.

Posted on 2011-02-15
19
394 Views
Last Modified: 2012-06-21
I would like to include a progress monitor or progress bar in my application.  The issue I am having is that the GUI code and the processing code needs to be kept separate.  In the progress monitor examples that I have seen, I usually see the processing done inside the doInBackground() method.  However, I already have non-GUI code implemented that I want to keep separate.  Also, in the future, I may want to create a simple command line application that uses that same processing code so I want to keep it out of the GUI.  I'm attaching a very simple example of what I'm talking about.  This application simply computes prime numbers.  For the GUI there is a MainFrame.java file which includes the MainFrame class and the Task class.  For processing, there is a separate file Primes.java.  I whipped together a Test.java which is just a simple command line program that also uses Primes.

Now, in class Task, I know the processing needs to be done in the doInBackground() method.  I'm doing this when I call getPrimes(max).  In getPrimes(), a new Primes object is created and the prime numbers are computed.

To update progress, I need to somehow have Primes return data back to the Task object so it can increment that progress monitor.  That is where I need assistance.  I do not know how to get send this data from the processing code back to the Task, sort of like a call back method.

As you can see now, I am just running getPrimes(max) after each progress update so it is computing all prime numbers, setting progress, computering all prime numbers, setting progress, and so on.

So, exactly how can a function in Primes send progress information back to the task?  Keep in mind, that the key is to keep the GUI separate from the primes processing.








// MainFrame.java
 import javax.swing.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 import java.beans.*;
 import java.util.concurrent.ExecutionException;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeEvent;
 import java.util.Random;

 public class MainFrame extends JFrame
                        implements PropertyChangeListener
 {
    private final JLabel label = new JLabel();
    private final JTextField textField = new JTextField();
    private final JButton button = new JButton( "Get Primes" );

    // Progress monitor stuff
    private final ProgressMonitor progressMonitor = new ProgressMonitor(MainFrame.this, "Computing ...", "", 0, 100);
    private Task task = null;

    public MainFrame()
    {
       super();
       setLayout(new BorderLayout());

       // initialize panel to get a number from the user
       JPanel topPanel = new JPanel();
       topPanel.add(new JLabel("find primes less than "));
       textField.setColumns(5);
       topPanel.add(textField);

 ////////////////////////////////////////////////////////////////////////////////////

       button.addActionListener(

          new ActionListener()
          {
             public void actionPerformed(ActionEvent e)
             {
                int input;
                progressMonitor.setProgress(0); // initial value
                label.setText("");

                try {
                   input = Integer.parseInt(textField.getText());
                }
                catch (NumberFormatException ex) {
                   label.setText("Enter a number");
                   return;
                }

                button.setEnabled(false);

                // Create the task and execute
                task = new Task(input);
                task.addPropertyChangeListener(MainFrame.this);
                task.execute();

             }
          });

 ////////////////////////////////////////////////////////////////////////////////////

       topPanel.add(button);
       add(topPanel, BorderLayout.NORTH);
       setSize(350,100);
       setVisible(true);
    }


    public void propertyChange(PropertyChangeEvent evt) {
       if ("progress" == evt.getPropertyName()) {
          int progress = (Integer) evt.getNewValue();
          progressMonitor.setProgress(progress);
          String message =
              String.format("Completed %d%%.\n", progress);
          progressMonitor.setNote(message);
          if (progressMonitor.isCanceled() || task.isDone()) {
              Toolkit.getDefaultToolkit().beep();
              if (progressMonitor.isCanceled()) {
                  task.cancel(true);
              } else {
              }
              button.setEnabled(true);
          }
       }
    }

    public void getPrimes(int n)
    {
       Primes p = new Primes(n);
       p.computePrimes();
    }

    public class Task extends SwingWorker<Void, Void>
    {
       private final int max;

       public Task(int n)
       {
          max = n;
       }

       @Override
       public Void doInBackground()
       {
          Random random = new Random();
          int progress = 0;
          setProgress(0);
          try {
             Thread.sleep(1000);
             int i = 0;
             while (progress < max && !isCancelled()) {
                getPrimes(max);
                Thread.sleep(random.nextInt(1000));
                setProgress(100 * (i++)/max);
             }
          }
          catch (InterruptedException ignore) {
          }
          return null;
       }

       @Override
       public void done() {
          Toolkit.getDefaultToolkit().beep();
          button.setEnabled(true);
       }
    }

    public static void main(String[] args)
    {
       MainFrame application = new MainFrame();
       application.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
 }

//Primes.java
 import java.util.Random;

 public class Primes
 {
    private int n;
    private final boolean primes[];
    private final Random generator = new Random();

    public Primes(int max)
    {
       primes =  new boolean[max];

       for (int i = 0; i < max; i++) {
          primes[i] = true;
       }
    }

    public int computePrimes()
    {
       int count = 0;

       for (int i=2; i < primes.length; i++) {

          if (primes[i]) {
             count++;

             for (int j = i + i; j < primes.length; j += i) {
                primes[j] = false;
             }
          }
       }
       return count;
    }

    public void print()
    {
      for (int i =2; i < primes.length; i++) {
         try {
            Thread.currentThread().sleep(generator.nextInt(10));
          }
          catch ( InterruptedException ex ) {
             System.out.println("Worker thread interrupted");
             return;
          }

          if (primes[i]) {
             System.out.println(i);
          }
       }
    }
 }

// Test.java (command line)
 class Test
 {
    public static void main(String[] args)
    {
       int n = 1111;

       Primes p = new Primes(n);

       System.out.println(p.computePrimes());
       p.print();
    }
 }

Open in new window

0
Comment
Question by:mock5c
  • 11
  • 8
19 Comments
 
LVL 92

Expert Comment

by:objects
ID: 34902599
>                 getPrimes(max);

to seperate it you would just replace that line with a call to another class that handles the processing

generator.getPrimes(max);

To get progress status back you could use a listener

generator.getPrimes(max, mylistener);
0
 

Author Comment

by:mock5c
ID: 34903187
Could you elaborate a bit more on this generator?  Do I need to create another class?
0
 
LVL 92

Expert Comment

by:objects
ID: 34903274
its a seperate class for handling generating primes to better seperate your gui from the business logic
0
 
LVL 92

Expert Comment

by:objects
ID: 34903287
sorry didn't see your Primes class, I'd just call that directly from your worker and get rid of that getPrimes() method
0
 

Author Comment

by:mock5c
ID: 34903324
Here's the thing... whether I call Primes directly or not, it computes all of the primes, then sets progress, computes all primes again, sets progress, and so on.  What I need is a way to intermittently send back progress so that the progress bar is refreshed as the primes are being computed.
0
 
LVL 92

Expert Comment

by:objects
ID: 34903345
thats what the listener is for
0
 
LVL 92

Expert Comment

by:objects
ID: 34903350
you could use a PropertyListener or implement your own
http://download.oracle.com/javase/tutorial/uiswing/events/propertychangelistener.html
0
 

Author Comment

by:mock5c
ID: 34909972
So what I've done to start is update the Primes.java class.  I added methods to get/set a variable prog.  Also added addPropertyChangeListener and removePropertyChangeListener.


And then in doInBackground()

@Override
public Void doInBackground()
{
   Random random = new Random();
   int progress = 0;
   setProgress(0);
   final Primes p = new Primes(max);
   p.addPropertyChangeListener(
      new PropertyChangeListener()
      {
         public void propertyChange(PropertyChangeEvent e)
         {
            if (e.getPropertyName().equals("prog"))
            {
               System.out.println("Prog changed");
               //setProgress(100 * p.getProg()/max);
            }
         }
      }
   );
   try {
      Thread.sleep(1000);
      int i = 0;
      getPrimes(max);
      setProgress(100 * (int)(p.getProg())/max);

      //while (p.getProg() < max && !isCancelled()) {
         //Thread.sleep(random.nextInt(1000));

         //setProgress(100 * (i++)/max);
         //setProgress(100 * (int)(p.getProg())/max);
      //}
   }
   catch (InterruptedException ignore) {
   }
   return null;
}

I think I'm a ways off but am I getting closer?  I don't see any progress updated in the progress monitor.

//Primes.java
 import java.util.Random;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;


 public class Primes
 {
    private int n;
    private final boolean primes[];
    private final Random generator = new Random();
    private double prog;

    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    public Primes(int max)
    {
       primes =  new boolean[max];

       for (int i = 0; i < max; i++) {
          primes[i] = true;
       }
    }

    public double getProg()
    {
       return this.prog;
    }

    public void setProg(double prog)
    {
       double old = this.prog;
       this.prog = prog;
       this.pcs.firePropertyChange("prog", old, prog);
    }

    public int computePrimes()
    {
       int count = 0;
       prog = 0;

       for (int i=2; i < primes.length; i++) {

          setProg(100 * i/primes.length);
          System.out.println(getProg());

          if (primes[i]) {
             count++;

             for (int j = i + i; j < primes.length; j += i) {
                primes[j] = false;
             }
          }
       }
       return count;
    }

    public void print()
    {
      for (int i =2; i < primes.length; i++) {
         try {
            Thread.currentThread().sleep(generator.nextInt(10));
          }
          catch ( InterruptedException ex ) {
             System.out.println("Worker thread interrupted");
             return;
          }

          if (primes[i]) {
             System.out.println(i);
          }
       }
    }

    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
       this.pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
       this.pcs.removePropertyChangeListener(listener);
    }

 }

Open in new window

0
 
LVL 92

Expert Comment

by:objects
ID: 34911518
your not updating the progress bar in your listener
make sure you do the update from the EDT

http://helpdesk.objects.com.au/java/how-do-i-update-my-gui-from-any-thread
http://helpdesk.objects.com.au/java/simple-progress-bar-example
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:mock5c
ID: 34913070
should the PropertyChangeListener be inside doInBackground?  

p.addPropertyChangeListener(
   new PropertyChangeListener()
   {
      public void propertyChange(PropertyChangeEvent e)
      {
         if (e.getPropertyName().equals("prog"))
         {
            try {
               Thread.sleep(random.nextInt(1000));
               System.out.println("Prog changed");
               setProgress(100 * (int)p.getProg()/max);
            }
            catch (InterruptedException ignore) {
            }
         }
      }
   }
);

I'm trying to get the updated progress value "prog" from Primes object.  Is the above appropriate?  I've seen plenty of examples that use progress bars/monitors and it makes sense to me.  But I'm finding nothing where the processing is handled outside the GUI code, such as Primes.
0
 
LVL 92

Accepted Solution

by:
objects earned 500 total points
ID: 34913129
where you have it is fine, the only thing you need to change is to do the update from the EDT


EventQueue.invokeLater(new Runnable() {
public void run() {
     setProgress(100 * (int)p.getProg()/max);
}
});
0
 

Author Comment

by:mock5c
ID: 34913327
are publish() and process() required as well?
0
 
LVL 92

Expert Comment

by:objects
ID: 34913869
no
0
 

Author Comment

by:mock5c
ID: 34934371
I added the EventQueue as suggested and now the progress monitor appears to be working.  It is receiving the updated progress from the primes object.  It also looks like I do not need a PropertyChangeListener at all for the Task SwingWorker object so it is commented out.

What I don't understand is suppose I do have a PropertyChangeListener for task and a PropertyChangeListener for primes.  If the primes listener were to fire every time the primes progress is modified and if I were to set the primes listener to setProgress() in the task object, shouldn't that then fire the task listener, which could then update the progressMonitor bar?  It would be a simple workflow in that case.  I think when I first tried this scenario, it was printing that the primes progress changed throughout but the task progress only changed once at the very end.  It's not clear why that is.

//Primes listener
// This gets fired each time progress in Primes changes
                p.addPropertyChangeListener(
                   new PropertyChangeListener()
                   {
                      public void propertyChange(PropertyChangeEvent e)
                      {
                         if (e.getPropertyName().equals("progress"))
                         {
                               Thread.sleep(random.nextInt(100));
                                int newValue = (Integer) e.getNewValue();
                                System.out.println("Primes progress changed");
                               setProgress(p.getProgress); // This would set the task's progress
                         }
                   }
            });

// Task's listener
// This gets fired each setProgress() inside primes listener, right?
                task.addPropertyChangeListener(
                   new PropertyChangeListener()
                   {
                      public void propertyChange(PropertyChangeEvent e)
                      {
                         if (e.getPropertyName().equals("progress"))
                         {
                            System.out.println("Progess changed");
                            int newValue = (Integer) e.getNewValue();
                            progressMonitor.setProgress(newValue);  //This would set the monitor progress
                         }
                      }
                   }
                );

The other problem that I'm having is when the program ends or if I cancel and then I click on Get Primes button again, it will show changes in stdout but the progress monitor does not appear again.








//MainFrame.java
 import javax.swing.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 import java.beans.*;
 import java.util.concurrent.ExecutionException;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeEvent;
 import java.util.Random;
 import java.awt.EventQueue;

 public class MainFrame extends JFrame
 {
    private final JLabel label = new JLabel();
    private final JTextField textField = new JTextField();
    private final JButton button = new JButton( "Get Primes" );

    // Progress monitor stuff
    private final ProgressMonitor progressMonitor = new ProgressMonitor(MainFrame.this, "Computing ...", "", 0, 100);
    private Task task = null;


    public MainFrame()
    {
       super();
       setLayout(new BorderLayout());

       JPanel topPanel = new JPanel();
       topPanel.add(new JLabel("find primes less than "));
       textField.setColumns(5);
       topPanel.add(textField);

       button.addActionListener(

          new ActionListener()
          {
             public void actionPerformed(ActionEvent e)
             {
                progressMonitor.setProgress(0);
                label.setText("");

                int input;

                try {
                   input = Integer.parseInt(textField.getText());
                }
                catch (NumberFormatException ex) {
                   label.setText("Enter a number");
                   return;
                }

                // Create the task and execute
                task = new Task(input);

 /*
                task.addPropertyChangeListener(
                   new PropertyChangeListener()
                   {
                      public void propertyChange(PropertyChangeEvent e)
                      {
                         if (e.getPropertyName().equals("progress"))
                         {
                            System.out.println("Progess changed");
                            int newValue = (Integer) e.getNewValue();
                            //progressMonitor.setProgress(newValue);
                         }
                      }
                   }
                );
 */
                button.setEnabled(false);

                task.execute();

             }
          }
       );

       topPanel.add(button);
       add(topPanel, BorderLayout.NORTH);
       setSize(350,100);
       setVisible(true);

    }

    public void getPrimes(final int max)
    {
       final Random random = new Random();
       final Primes p = new Primes(max);

       p.addPropertyChangeListener(
          new PropertyChangeListener()
          {

            public void propertyChange(PropertyChangeEvent e)
            {
               if (e.getPropertyName().equals("progress"))
               {
                  try
                  {
                     Thread.sleep(random.nextInt(100));
                     int newValue = (Integer) e.getNewValue();
                     System.out.println("Primes progress changed");

                     EventQueue.invokeLater(new Runnable()
                     {
                        public void run()
                        {
                           progressMonitor.setProgress(p.getProgress());
                        }
                     });
                  }
                  catch (InterruptedException ignore) {}
               }
            }
         }
      );

      p.computePrimes();
   }


////////////////////////////////////////////////////////////////////////////////////


   public class Task extends SwingWorker<Void, Void>
   {
      private final int max;

      // CONSTRUCTOR
      public Task(int n)
      {
         max = n;
      }

      @Override
      public Void doInBackground()
      {
         final Random random = new Random();
         Primes p = new Primes(max);

         int progress = 0;
         setProgress(0);

         try {
            Thread.sleep(random.nextInt(1000));
            getPrimes(max);
         } catch (InterruptedException ignore) {
            System.out.println("Interruption Exception caught");
         }

         return null;
      }


      // After doInBackground() returns, this method is called from the EDT.
      @Override
      public void done() {
         Toolkit.getDefaultToolkit().beep();
         button.setEnabled(true);
      }
   }


////////////////////////////////////////////////////////////////////////////////////

   public static void main(String[] args)
   {
      MainFrame application = new MainFrame();
      application.setDefaultCloseOperation(EXIT_ON_CLOSE);
   }
}

//Primes.java

import java.util.Random;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;


public class Primes
{
   private int n;
   private final boolean primes[];
   private final Random generator = new Random();
   private int progress;

   private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

   public Primes(int max)
   {
      primes =  new boolean[max];

      for (int i = 0; i < max; i++) {
         primes[i] = true;
      }
   }

   public int getProgress()
   {
      return this.progress;
   }

   public void setProgress(int progress)
   {
      int old = this.progress;
      this.progress = progress;
      this.pcs.firePropertyChange("progress", old, progress);
   }

   public int computePrimes()
   {
      int count = 0;
      progress = 0;

      for (int i=2; i < primes.length; i++) {

         setProgress(100 * i/primes.length);

         if (primes[i]) {
            count++;

            for (int j = i + i; j < primes.length; j += i) {
               primes[j] = false;
            }
         }
      }
      return count;
   }

   public void print()
   {
     for (int i =2; i < primes.length; i++) {
        try {
           Thread.currentThread().sleep(generator.nextInt(10));
         }
         catch ( InterruptedException ex ) {
            System.out.println("Worker thread interrupted");
            return;
         }
      }
   }

   public void addPropertyChangeListener(PropertyChangeListener listener)
   {
      this.pcs.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener listener)
   {
      this.pcs.removePropertyChangeListener(listener);
   }
}

Open in new window

0
 
LVL 92

Expert Comment

by:objects
ID: 34935262
you are adding a propertychangelistener u=in line 92

adding a propertychangelistener to your task has no effect because you don't fire the property change from within your task. so no event gets generated
0
 

Author Comment

by:mock5c
ID: 34935538
Yes, I do not fire the property change from within my task.  The code I attached used the EventQueue technique that you suggested.   But what I was was asking is if I did fire the property change from within my task, why doesn't that work?  See the code I pasted in the body of my previous message.
0
 
LVL 92

Expert Comment

by:objects
ID: 34935597
I stiill dont see where you call firePropertyChange()
0
 

Author Comment

by:mock5c
ID: 34937746
I think I misworded my question.

When primes are computed, primes.progress is updated frequently by calling primes.setProgress() (which does firePropertyChange).  Everytime this setProgress is invoked, an event is generated. The listener on the primes object listens for these events.  Inside this primes listener, I call setProgress(p.getProgress()).  Doesn't this setProgress inside the primes listener implicitly call the firePropertyChange and generate an event that the task listener is listening for?  If that is the case, then each time this setProgress is invoked.  Then inside the task listener, there is a call to progressMonitor.setProgress() to update the progress monitor.

Does that make sense?

0
 
LVL 92

Expert Comment

by:objects
ID: 34938847
> Doesn't this setProgress inside the primes listener implicitly call the firePropertyChange and generate an event that the task listener is listening for?

no it doesn't
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Java contains several comparison operators (e.g., <, <=, >, >=, ==, !=) that allow you to compare primitive values. However, these operators cannot be used to compare the contents of objects. Interface Comparable is used to allow objects of a cl…
Introduction This article is the first of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article explains our test automation goals. Then rationale is given for the tools we use to a…
This tutorial covers a practical example of lazy loading technique and early loading technique in a Singleton Design Pattern.
This tutorial explains how to use the VisualVM tool for the Java platform application. This video goes into detail on the Threads, Sampler, and Profiler tabs.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now