Animated GIF in TableViewer

I’m quite attached to TableViewer and the nice separation between UI and data so I tend to use it pretty much every time I can. However when faced with having to indicate progress in a table row I got into trouble. If the table was implemented just in SWT it would not be much of a problem as you get pretty much full control. It’s not that easy when using a JFace. I decided to hook into the label provider and use an animated image to indicate progress. It proved to be a nice solution so I’ve exemplified it for your enjoyment. Note that the housekeeping is very much left out in order to keep the code short.

The pretty GIF was lifted off http://www.ajaxload.info/.

First we have the data class. It’s simply represent a person and whether or not that person is busy.

public class Data {
  boolean busy;
  String firstName;
  String lastName;

  public Data(String firstName, String lastName, boolean busy) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.busy = busy;
  }
}

We use the SWT.VIRTUAL flag on the table in order to make sure we’re notified whenever new data is added to the viewer. When this happens we store the table item to a list. The GIF is loaded from a file and we store the information in two arrays. One for the images and another for the delays between images. The job is done by a thread that will update the image of each table item that represents a busy person.

public class TableViewerAnimation {

 /**
  * The label provider that will animate the GIF.
  */
 private class MyLabelProvider extends LabelProvider implements
   ITableLabelProvider {
  /**
   * The thread that will do the animation.
   */
  private class TableImageUpdater extends Thread {
   /** Items to update */
   private final ArrayList list = new ArrayList();
   /** The current image number */
   private int progress = 0;

   @Override
   public void run() {
    while (true) {
     long currentTime = System.currentTimeMillis();
     while (currentTime + delay[progress] * 10 > System
       .currentTimeMillis()) {
      try {
       Thread.sleep(delay[progress]);
      } catch (InterruptedException e) {
       e.printStackTrace();
      }
     }
     Display.getDefault().asyncExec(new Runnable() {
      @Override
      public void run() {
       progress++;
       if (progress >= images.length) {
        progress = 0;
       }
       for (TableItem item : list) {
        Data d = (Data) item.getData();
        if (d.busy) {
         item.setImage(images[progress]);
        }
       }
      }
     });
    }
   }
  }

  /** The delay between images */
  int[] delay;
  /** The progress images */
  Image[] images;
  /** Thread to update images */
  private TableImageUpdater updater;

  public MyLabelProvider() {
   initializeImages();
   installPainter();
  }

  public Image getColumnImage(Object element, int columnIndex) {
   if (columnIndex == 0 && ((Data) element).busy) {
    return images[0];
   }
   return null;
  }

  public String getColumnText(Object element, int columnIndex) {
   Data d = (Data) element;
   switch (columnIndex) {
   case 0:
    return d.firstName;
   case 1:
    return d.lastName;
   }
   return "";
  }

  private void initializeImages() {
   ImageLoader loader = new ImageLoader();
   loader.load(getClass().getResourceAsStream("PROGRESS.gif"));
   images = new Image[loader.data.length];
   delay = new int[loader.data.length];
   for (int x = 0; x < images.length; x++) {
    ImageData imageData = loader.data[x];
    delay[x] = loader.data[x].delayTime;
    images[x] = new Image(Display.getCurrent(), imageData);
   }
  }

  private void installPainter() {
   updater = new TableImageUpdater();
   updater.start();
   // Install the updater on each new item in the table as soon as the
   // data is set.
   viewer.getTable().addListener(SWT.SetData, new Listener() {
    public void handleEvent(Event event) {
     TableItem item = (TableItem) event.item;
     updater.list.add(item);
    }
   });
  }
 }

 public static void main(String[] args) {
  new TableViewerAnimation().run();
 }

 private final Shell shell;

 private final TableViewer viewer;

 public TableViewerAnimation() {
  super();

  shell = new Shell(Display.getDefault(), SWT.SHELL_TRIM);
  shell.setText("Famous generals");
  shell.setLayout(new FillLayout());
  // We use SWT.VIRTUAL here so that the SWT.SetData event will be called.
  Table table = new Table(shell, SWT.BORDER | SWT.SINGLE
    | SWT.FULL_SELECTION | SWT.VIRTUAL);
  table.setHeaderVisible(true);

  TableColumn column = new TableColumn(table, SWT.LEFT);
  column.setText("First Name");
  column.setWidth(80);

  column = new TableColumn(table, SWT.LEFT);
  column.setText("Last Name");
  column.setWidth(180);

  viewer = new TableViewer(table);

  viewer.setContentProvider(new ArrayContentProvider());
  viewer.setLabelProvider(new MyLabelProvider());
  shell.pack();
 }

 public void run() {
  Display display = Display.getCurrent();
  shell.pack();
  shell.open();

  Data[] data = new Data[] { new Data("Hannibal", "Barca", false),
    new Data("Alexander", "The Great", true),
    new Data("Julius", "Caesar", false),
    new Data("Napoleon", "Bonaparte", true),
    new Data("Sun", "Tzu", false) };

  viewer.setInput(data);

  while (!shell.isDisposed())
   if (!display.readAndDispatch())
    display.sleep();
  display.dispose();
 }

}

4 Comments

  1. It's not really needed to use SWT.Virtual. If you'd use the new CellLabelProvider-Infrastructure you get access to ViewerCell which has access to a ViewerRow which abstracts TableItem/TreeItem/GridItem/…

  2. When your GUI thread is busy all your spinners will be frozen. Unfortunately there is no solution for that. Only to create a separate thread without access to UI(I mean widgets) where you will be updating the progress. But for example it works nice 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.