DeferredContentProvider: My new favourite thing

I’ve built two tables over the past two days and both of them are pretty expensive to populate. Leaving the user waiting while you run off and fetch all the data before you can display the table is bad form. To keep in your users’ good graces, consider using the DeferredContentProvider.

DeferredContentProvider is part of JFace, and works well with the JFace TableViewer (I assume that it can also work with a TreeViewer, but haven’t tried it yet). Rather than try and do a bad job at explaining how it works, here’s an example of an Eclipse view that employs one:

...
public class SampleView extends ViewPart {
  private TableViewer viewer;
  private SetModel files = new SetModel();
  private Job findFilesJob;
  ...
  public void createPartControl(Composite parent) {
    viewer = new TableViewer(parent, SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL);
    viewer.setContentProvider(new DeferredContentProvider(new Comparator() {
      public int compare(File file1, File file2) {
        return file1.getName().compareTo(file2.getName());
      }
    }));
    viewer.setLabelProvider(new ViewLabelProvider());
    viewer.setInput(files);

    startFindFilesJob();
  }
  ...
}

This view lists—in a table—all the files in your file system. As you might expect, this can take a while. Starting from the top of the createPartControl method, you’ll notice that I’ve created the TableViewer using the SWT.VIRTUAL style: this is a requirement of use for the DeferredContentProvider (it configures the table to ask for the bits it needs to display as it needs them, rather than getting them all up front; more information here).

The content provider for the TableViewer is set to a new instance of DeferredContentProvider. The solitary constructor requires that a Comparator be provided: this Comparator, which must be provided, tells the content provider how to sort the items in the table. If you want to change how items are sorted, you need to tell the content provider, not the TableViewer.

The next bit is the input. The input tells the table what to display. The input has to be something that implements IConcurrentModel. For our purposes, an instance of the SetModel class (which has been created in the field named “files”) does what we need.

The last thing the method does is start a Job to populate the table via the startFindFilesJob method:

private void startFindFilesJob() {
  findFilesJob = new Job("Find Files") {
    @Override
    protected IStatus run(IProgressMonitor monitor) {
      monitor.beginTask("Find files", IProgressMonitor.UNKNOWN);
      for (File root : File.listRoots()) {
        findFiles(monitor, root);
      }
      if (monitor.isCanceled()) return Status.CANCEL_STATUS;
      return Status.OK_STATUS;
    }
  };
  findFilesJob.setPriority(Job.DECORATE);
  findFilesJob.schedule();
}

Note that this very simple example starts populating itself when it is opened, and I haven’t provided any means of restarting the population process other than to close and reopen the window.

Creating the job is pretty straightforward. I’ve decided to set the priority of the job to the lowest setting (Job.DECORATE) because experience has shown that it takes a very long time and I want the rest of the workbench to be as responsive as possible. The job, as defined above, will appear in the Progress view where is can easily be canceled. There’s more information about the Jobs API here.

The findFiles method recursively discovers the files and adds them to the SetModel (highlighted):

void findFiles(IProgressMonitor monitor, File root) {
  if (monitor.isCanceled()) return;
  File[] children = root.listFiles();
  if (children == null) return;
  files.addAll(children);
  for (File file : children) {
    findFiles(monitor, file);
  }
}

When the addAll method is called, the magic happens and the table is updated. It’s pretty neat to watch. Hypnotic even.

For completeness, the dispose method stops the job (no need to keep doing the work if the user closes the view):

@Override
public void dispose() {
  findFilesJob.cancel();
}

For what I’m doing, the SetModel seems to work well. I believe that this is true only because I’m updating it in a single thread. A quick browse through the code leads me to believe that it would break if multiple threads tried updating it at the same time. So keep that in mind.

I’m using the DeferredContentProvider on a plug-in that scans an IFileStore for image files and displays them (right now, I have it scanning eclipse.org’s CVS server). The deferred loading of the images is darned cool to watch. Once this reaches a reasonable level of maturity, I’ll let you know.

This entry was posted in Uncategorized. Bookmark the permalink.

5 Responses to DeferredContentProvider: My new favourite thing

  1. Boris Bokowski says:

    Hi Wayne,

    I hate to spoil the fun, but the code in org.eclipse.jface.viewers.deferred has a couple of bugs that went unfixed for quite some time. Partly due to lack of resources, partly because we didn’t have an early adopter who pushed us to fix the bugs, and partly due to mismatches between how SWT works and what the code in this package assumes about how to know which subset of the table is currently showing.

    If people are interested in what the problems are, send me an e-mail and I can point them at a list of bugs in that area. See also: http://wiki.eclipse.org/Platform_UI/How_to_Contribute

    Boris

  2. Wayne Beaton says:

    Don’t spoil the fun, Boris. I’ve only been playing with it for a few days and haven’t had too many problems. I did notice that DeferredContentProvider and OwnerDrawTableColumns don’t seem to work very well (a lot of rows go undrawn).

  3. Scott Lewis says:

    Hi Wayne,

    It might be interesting to try the DeferredContentProvider with ECF’s new asynchronous remote browse API:

    http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/filetransfer/IRemoteFileSystemBrowserContainerAdapter.html

    It runs a separate job for each sendBrowseRequest. Results are reported asynchronously via the IRemoteFileSystemListener.

    We’ve got providers for all the URL-supported protocols (plain http doesn’t support directory browsing of course) and efs. It runs on CDC 1.0/Foundation 1.0 EE. Other parts of this API are being used for the Equinox p2 work, so it will soon be in the platform.

    The async browsing stuff is brand new and still in flux, so any desired additions/changes could be made. Please LMK.

  4. David Kyle says:

    Here is a quick and dirty list of DeferredContentProvider bugs.

    http://tinyurl.com/yvm3py

    I’m a bit bummed out about the problems. The API looks perfect for solving a performance issue with a few of our viewers.

  5. Wayne Beaton says:

    Scott: sounds very cool. Maybe we can work out some kind of example application for EEP. Now, all I have to do is get that project proposal finished…

Leave a comment