This tutorial assumes what you have basic understanding of development for the Eclipse platform. Please see Eclipse 4 RCP Tutorial or Eclipse Plug-in Tutorial if you need any basic information.
By default Eclipse uses one Thread
to run all the code instructions. This Thread
also creates the event loop for the user interface (UI) and is the only Thread
that is allowed to interact with the UI.
This Thread
is called the UI thread or main thread. It is created by the org.eclipse.swt.widgets.Display
class.
If another Thread
, which is not the UI thread, tries to update the UI, the SWT framework will raise a SWTException
exception.
org.eclipse.swt.SWTException: Invalid thread access
All events in the user interface will be executed one after another. If you perform a long running operation in the UI thread, the user interface will not respond to any user interaction.
Therefore it is important not to block this thread, e.g. with network or file access otherwise the user interface will appear frozen.
All long running operations should be performed in a separate Thread
.
The Eclipse framework provides ways for a Thread
to synchronize itself with the user interface. It also provides the Eclipse Jobs Framework which allows you to run operations in the background using the Eclipse platform.
The org.eclipse.e4.ui.di
plug-in contains the UISynchronize
class. An instance of this class is in the Eclipse 4 context and can be injected into an Eclipse 4 application via dependency injection.
UISynchronize
also provides the syncExec()
and asyncExec()
methods to synchronize with the main thread.
Using UISynchronize
is the preferred method rather than using the Display
class, as this allows a consistent programming model using dependency injection.
The Eclipse Jobs API provides support for running background processes. It allows you to provide feedback about the progress of the Job
to the user via a progress indicator.
The important parts of the Job API are:
-
IJobManager - Schedules the jobs
-
Job - The individual task to perform
-
IProgressMonitor - Interface to communicate information about the status of your Job.
You create a Job
via the following:
Job job = new Job("My Job") { @Override protected IStatus run(IProgressMonitor monitor) { // Do something long running //... // If you want to update the UI Display.getDefault().asyncExec(new Runnable() { @Override public void run() { // Do something in the user interface // e.g. set a text field } }); return Status.OK_STATUS; } }; // Start the Job job.schedule();
If you want to update the UI from a Job
you still need to use the Display.syncExec()
or asyncExec()
methods.
You can set the Job
priority via the job.setPriority()
method using the constants defined in the Job
class, e.g. Job.SHORT
, Job.LONG
, Job.BUILD
and Job.DECORATE
.
The job scheduler will use these priorities to determine in which order the Jobs
are scheduled. For example Jobs
with the priority Job.SHORT
are scheduled before jobs with Job.LONG
. Jobs
with the priority Job.DECORATE
are scheduled after all other jobs are finished. Check the JavaDoc of the Job
class for details.
Sometimes you simply want to give the user the feedback that something is running without using Threads
.
The easiest way to provide feedback is to change the cursor via the BusyIndicator.showWhile()
method call.
// Show a busy indicator while the runnable is executed BusyIndicator.showWhile(display, runnable);
If this code is executed, the cursor will change to a busy indicator until the Runnable
is done.
A more advanced approach is to use a ProgressMonitorDialog
together with an IRunnableWithProgress
.
In Eclipse 3.x API based projects you cannot use dependency injection to get the UISynchronize
instance injected.
In this case you can use the Display
object which provides the syncExec()
and asyncExec()
methods to update the user interface from another Thread
.
// Update the user interface asynchronously Display.getDefault().asyncExec(new Runnable() { public void run() { // ... do any work that updates the screen ... } }); // Update the user interface synchronously Display.getDefault().syncExec(new Runnable() { public void run() { // Do any work that updates the screen ... // Remember to check if the widget // Still exists // Might happen if the Part was closed } });
The IProgressMonitor
object can be used to report progress. Use the beginTask()
method to define the total units of work and the worked()
method to report the additional units of work.
Job job = new Job("My Job") { @Override protected IStatus run(IProgressMonitor monitor) { // Set total number of work units monitor.beginTask("Doing something time consuming here", 100); for (int i = 0; i < 5; i++) { try { // Sleep a second TimeUnit.SECONDS.sleep(1) monitor.subTask("I'm doing something here " + i); // Report that 20 units are done monitor.worked(20); } catch (InterruptedException e1) { e1.printStackTrace(); return Status.CANCEL_STATUS; } } System.out.println("Called save"); return Status.OK_STATUS; } }; job.schedule();
In Eclipse 4 you can report progress by implementing the IProgressMonitor
interface.
You can for example add a ToolItem
to a Toolbar of your Window Trim in your application model. This ToolItem
can implement the IProgressMonitor
interface.
This is demonstrated in the following example.
package com.example.e4.rcp.todo.ui.composites; import javax.annotation.PostConstruct; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.ProgressBar; public class MyToolItem implements IProgressMonitor { private ProgressBar progressBar; @PostConstruct public void createControls(Composite parent) { System.out.println("ToolItem"); progressBar = new ProgressBar(parent, SWT.SMOOTH); progressBar.setBounds(100, 10, 200, 20); } @Override public void worked(final int work) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { System.out.println("Worked"); progressBar.setSelection(progressBar.getSelection() + work); } }); } @Override public void subTask(String name) { } @Override public void setTaskName(String name) { } @Override public void setCanceled(boolean value) { } @Override public boolean isCanceled() { return false; } @Override public void internalWorked(double work) { } @Override public void done() { System.out.println("Done"); } @Override public void beginTask(String name, int totalWork) { System.out.println("Starting"); } }
This new element can be accessed via the model service and used as an IProgressMonitor
for the job.
// EModelService injected as service // Mapplication injected as application Job job = new Job("My Job") { // As before }; // Setting the progress monitor IJobManager manager = job.getJobManager(); // ToolItem has the ID "statusbar" in the model MToolControl element = (MToolControl) service.find("statusbar", application); Object widget = element.getObject(); final IProgressMonitor p = (IProgressMonitor) widget; ProgressProvider provider = new ProgressProvider() { @Override public IProgressMonitor createMonitor(Job job) { return p; } }; manager.setProgressProvider(provider); job.schedule();
A more advanced implementation could for example implement a Progress Monitoring OSGi Service and report progress to the user interface via the EventAdmin service.
To activate progress reporting in the status line in Eclipse 3.x you have to activate progress reporting in preWindowOpen()
method of the WorkbenchWindowAdvisor
.
Create a new Eclipse plug-in project "de.vogella.jobs.first" with a View
and a Button
included in this View
.
Create the following MySelectionAdapter
class.
package de.vogella.jobs.first.parts; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; public class MySelectionAdapter extends SelectionAdapter { private final Shell shell; public MySelectionAdapter(Shell shell) { this.shell = shell; } @Override public void widgetSelected(SelectionEvent e) { Job job = new Job("First Job") { @Override protected IStatus run(IProgressMonitor monitor) { doLongThing(); syncWithUi(); // Use this to open a Shell in the UI thread return Status.OK_STATUS; } }; job.setUser(true); job.schedule(); } private void doLongThing() { for (int i = 0; i < 10; i++) { try { // We simulate a long running operation here Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing something"); } } private void syncWithUi() { Display.getDefault().asyncExec(new Runnable() { public void run() { MessageDialog.openInformation(shell, "Your Popup ", "Your job has finished."); } }); } }
Add an instance of MySelectionAdapter
as SelectionListener
to your Button
.
Button button = new Button(parent, SWT.PUSH); button.addSelectionListener(new MySelectionAdapter(shell));
To access the Shell
in Eclipse 3.x you can use the getSite().getShell()
method call. In Eclipse 4 you declare a field and let Eclipse inject the Shell
.