Loading Eclipse Search ...

Subscribe To

Unless stated, all the text contents of this site is available under Eclipse Public License

How To Guides

Article Index
Progress Bars in Eclipse UI
Sub Tasks
Properties, Action & Command
All Pages

In the last tip, I said the top most mistake done by Eclipse plug-in developers is running long running operations in the UI thread. Assuming that you are running it in a non-UI thread, how to show the progress of the execution? Obviously thru progress monitors. But how many different ways are there to show a progress monitor? Lets see them in this tip.

 

The first option is ProgressMonitorDialog. You have to create a IRunnableWithProgress and use the run method:

ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);    
dialog.run(true, true, new IRunnableWithProgress(){
     public void run(IProgressMonitor monitor) {
         monitor.beginTask("Some nice progress message here ...", 100);
         // execute the task ...
         monitor.done();
     }
 });

Result:

ProgressMonitorDialog

 

As you can see, the dialog is kind-of blocking the user. This dialog is advisable only in the rare cases where the user have to wait till the operation is completed. In most other cases, you should prefer running a Job. A Job is not blocking to the user and can still show the progress. The progress is available in the Progress View.

  Job job = new Job("My new job") {
     @Override
     protected IStatus run(IProgressMonitor monitor) {
         monitor.beginTask("Some nice progress message here ...", 100);
         // execute the task ...
         monitor.done();
         return Status.OK_STATUS;
     }
 };
 job.schedule();

Result:

Job in Progress View

If you make the job as a user job by calling setUser(true), the progress dialog will be opened, where the user can chose to run the job as a background job. If the user doesn't want to see this dialog again, he can select to 'Always run in background': When the job is initiated by the user interacting with a workbench part (like clicking a button in a view), a better way to schedule the job is thru the site's IWorkbencSiteProgressService.schedule. When the job begins to execute, the workbench part can show it visually. The default behaviour is to italize the part name:

 

WorkbenchPart.showBusy

 

But you can customize it by overriding the showBusy() method in your WorkbenchPart:

 public void showBusy(boolean busy) {
     super.showBusy(busy);
     if(busy)
         setPartName("I'm doing a job right now...");
     else
         setPartName("Sample View");
 }

Result:

WorkbenchPart.showBusy

 

In case your action doesn't have any WorkbenchPart associated with it, you can use the IWorkbenchWindow.run(...) to show the progress: IWorkbenchWindow.run

 

If you are running a long running operation in a wizard/wizardPage you should consider running it thru getContainer().run(). This will show the progressBar right there in the wizard dialog itself:

 

progressbar in wizard

If you are updating the UI elements in the wizardPage during the execution, you have to note that if you do a setEnabled() on any widget, it won't reflect. Its because the container registers the enabled state of the widgets before execution; disables the widgets; executes the task and restores the widget's enabled state to the saved value. So if you have changed the state during the operation, it will be overwritten by the container. In case, you can't display a progressbar, you should at least use BusyIndicator.showWhile() and display the busy cursor to show that some operation is being executed. Else the user might be wondering why the UI is frozen.

 


 

When inspecting a bug, I found a interesting thing on Progress Monitors. We know monitor.worked() increments the progress bar, but how do we change the text to update the current subtask? The initial text is set by the beginTask() method and it should be called only once. I digged into the IProgressMonitor and found the subtask() method:

IRunnableWithProgress operation = new IRunnableWithProgress() {

public void run(IProgressMonitor monitor) {

monitor.beginTask("Main task running ...", 5);
for (int i = 0; i < 5; i++) {
monitor.subTask("Subtask # " + i + " running.");
runSubTask(new SubProgressMonitor(monitor, 1), i);
}
}

};

Now the question is what happens when the runSubTask() method sets another subTask on the SubProgressMonitor?

private void runSubTask(IProgressMonitor monitor, int subTaskId) {

monitor.beginTask("Sub task running", 10);
for (int i = 0; i < 10; i++) {
monitor.subTask("Inside subtask, " + i + " out of 10");
// do something here ...
monitor.worked(1);
if (monitor.isCanceled())
throw new OperationCanceledException();
}
monitor.done();
}

}

Basically the SubProgressMonitor's subTask() overwrites the parent's subTask(). Thats the default behaviour. You can customize it with the style bits provided in the SubProgressMonitor: If you want to append the SubProgressMonitor's subTask info, use the style PREPEND_MAIN_LABEL_TO_SUBTASK:

new SubProgressMonitor(monitor, 1, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK), i);

Else if you want to ignore it altogher then use the SUPPRESS_SUBTASK_LABEL style:

new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), i);

How do you show the partial results of a background job that is not yet completed? Quick answer is by adding an Action to that Job. The long answer is this tip. The Progress view shows all the jobs that are scheduled. It also shows the progress bar which is updated with the amount of work done. Consider this snippet:

Job job = new Job("My Job") {
@Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("My job is working...", 100);
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {} // ignore
monitor.worked(1);
}
monitor.done();
return new Status(IStatus.OK, Activator.PLUGIN_ID, "Job finished");
}

};
job.schedule();

The Progress view shows this job like this:

 


 

Jobs allow user defined key-value pair properties to be set on them. The key would be QualifiedName and the value will be any Object. The Progress view identifies certain properties set on the job and acts accordingly. One of the properties is IProgressConstants.ACTION_PROPERTY. The value should be an IAction.

job.setProperty(IProgressConstants.ACTION_PROPERTY, new Action() {
@Override
public void run() {
MessageDialog.openInformation(new Shell(), "Job Status", "Some partial results processed can be displayed here");
}
});
}

When you set an action on a job as above, the Progress view finds it and gives you the visual hint:

The progress text becomes a hyperlink. When you click on it, the associated action will be executed. You can probably open up a dialog with the results of the job which is completed so far. As soon as the job is over, the Progress view removes it from the view. Is there a way to reuse this same action to show the complete set of results even after the job is finished? Yes. The Progress view understands another property called IProgressConstants.KEEP_PROPERTY. Its a boolean property, and when its set, the Job stays in the Progress view even after its finished.

job.setProperty(IProgressConstants.KEEP_PROPERTY, true);

Since the ProgressMonitor is done, the text for the hyperlink is now taken from the IStatus that is returned from the Job. All is fine when the job finishes normally. But what if the return status is an error? Progress view opens up a Dialog and shows you the status. It might be intrusive sometimes. Is there a way to suppress the dialog so that the user can go back to the Progress view and check the status? Again Yes. Just set the boolean property IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, there won't be any dialogs. The Progress view icon in the status bar will indicate the user that some job did not complete normally. When the user clicks on the job, can reuse the same action to display what went wrong.

job.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, true);

Did you see the other job in the above pictures? It has a nice looking icon. So how do we associate an icon with our job? Yes, what you think is right. The Progress view also understands an another property IProgressConstants.ICON_PROPERTY thru which you can set your icon:

job.setProperty(IProgressConstants.ICON_PROPERTY, getJobImageDescriptor());

There you go:

Next time you create a long running background job, consider setting these properties. Starting from 3.6 M3, you can associate a Command also. Its very similar - use the constant to set a property on the job:

ICommandService service = (ICommandService) serviceLocator.getService(ICommandService.class);
Command command = service.getCommand(commandId);
ParameterizedCommand parameterizedCommand = new ParameterizedCommand(command, null);
job.setProperty(IProgressConstants.COMMAND_PROPERTY, parameterizedCommand);

You have to specify either the ACTION_PROPERTY or COMMAND_PROPERTY, but not both. If you specify both, neither of them will get executed.

 

Original Entry