Tree and Table Viewer

Introduction to viewers

Why use the viewers?

The viewers provide an easy mechanism to display data in either a tree or table, and remove the complexity of trying to work out how to associate listeners with both.  Essentially you're provided with a clean API that makes it quite simple to display information.

How viewers work

Viewers at the highest level take two or three (depending on the viewer) things:

* Content Provider
* Label Provider (Must be provided for the table viewer)
* Input

Content Provider

A content provider tells the viewer how to interact with a piece of provided data.  So for example, given a java bean as input, we would need to look at this java bean in the content provider and tell the viewer which data we're interested in.

Given a table viewer declared as:

TableViewer tv = new TableViewer('''parent''');

And a list of entities such as:

List<Entity> people = ...

We would write a content provider to be something along the lines of:

tv.setContentProvider(new IStructuredContentProvider() {
        public Object[] getElements(Object element) {
           if (element instanceof List)
              return ((List) element).toArray();
        }
        public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
           // Don't need to be aware at this point of input changes
        }
   });
        
   tv.setInput(people); // set our list into the table viewer.

GOTCHA: The viewer API requires that your providers are set BEFORE you set the input for your viewer.

Obviously this is a very simple example - See the file viewer tutorial for a slightly more complex table provider.

Label Provider

Calling "toString" on an object often returns something like:

com.dan.rox.HelloWorld@23f12a

Which is helpful to almost no one. Displaying a table full of this is definately not going to tell users how many patients are going to die today.

As such, the viewers ask us for a label provider. So given a simple java object:

public class HelloWorld
{
       String familyName = "";
       String motherName = "";
       public void get...();
}

We can give the viewer a label provider which may return just "familyName" or perhaps both "familyName" and "motherName". If we're really keen we can also return associated images.

There are a couple of ways you can write label providers, however you need to be aware that the label provider for a tree and the label provider for a table are slightly different.

Given a table, with two columns (as shown in the hello world example above), we would write something like:

public class HelloWorldLabelProvider implements ITableLabelProvider
{
     public String getColumnText(Object obj, int column)
     {
           switch (column)
           {
              case 0:
                 return ((HelloWorld) obj).getFamilyName();
              case 1:
                 return ((HelloWorld) obj).getMotherName();
           }
      // Do something here because we didn't receive a column we're aware of
     }
      ...
}

Input

Well this is simple.  Input can be anything you want it to be.  Arrays, List, Services, you name it.  Provided your content provider knows how to interact with it, and return something meaningful to the viewer.

Of course, this doesn't mean you should start plugging your services into viewers. My recommendation would be Lists or tree root nodes to keep everything simple

Reminder: You set the input AFTER AND ONLY AFTER you've set your providers. (Blame Eclipse for that one)

This is a real tuff one kids:

Viewer.setInput(''your input object here'');

Viewer Limitations

* Tree and Table Viewers currently have a singleselection mode on by default.  If this is not appropriate for your control, you will need to change the selection mode via the selection model. However, the selection mechanism has only been tested with single selection mode.

Tutorial

Goal

Using the tree and table viewer we're going to attempt to create a file system browser.  So we want a tree on one side that lists the directory structure of our file system, and on the otherside, a table listing the files for a particular directory.

How

To acheive this, we're going to do the following:
* Implement a tree content provider
* Implement a tree label provider
* Implement a table content provider
* Implement a table label provider

Set up

Firstly, make sure you've got the following things:
 * 1 Working Workbench
 * 1 View ready for table and tree viewers
 * 2 Beers

For Added Fun

Try doing this tutorial in a RCP/SWT application!

The Tree Viewer

For this tutorial, we're going to be displaying stuff from the file system.  This means we'll be interacting with the Java "File" object.  As such, we need to tell our tree how to interact with this object.

Tree Content Provider

The file object is essentially tree based, and can provide us with a list of items for a particular place in the tree structure.  So we need our content provider to return the objects as appropriate.  As such, we should end up with something like the following:

public class FileTreeContentProvider implements ITreeContentProvider
{
      public Object getChildren(Object element)
      {
         Object kids = ((File) element).listFiles();
         return kids == null ? new Object\[0\] : kids;
      }
      
      public Object getElements(Object element)
      {
         return getChildren(element);
      }
      
      public boolean hasChildren(Object element)
      {
         return getChildren(element).length > 0;
      }

      public Object getParent(Object element)
      {
         return ((File)element).getParent();
      }
}

This gives us enough to start displaying some information. So lets give that a whirl.

In your "createPartControl" method, add the following:

// Tree viewer will automatically add the tree to the parent object
TreeViewer tv = new TreeViewer(parent);
tv.setContentProvider(new FileTreeContentProvider());
tv.setInput(new File("C:"));

And run it.

You should now see the tree appearing.

Tree Label Provider

The tree we've created with the above code isn't all that pretty.  Especially with it's qualified file paths and such.  By implementing a Label provider, we can make the information it displays a little easier to digest.

We basically want the tree to simply display the name of the file / element - much like you'd expect from the windows file explorer.  The code to acheive this should come off looking something like the following:

public class FileTreeLabelProvider extends LabelProvider
{
      public String getText(Object element)
      {
         return ((File) element).getName();
      }
}

So whats happening here?  The Viewer is going to call the ''getText(..)'' method, providing it with the current element that it's trying to render.  The getText method then happily returns the text/label it wants associated with this object.
Change your code to now look like the following:

TreeViewer tv = new TreeViewer(parent);
tv.setContentProvider(new FileTreeContentProvider());
tv.setLabelProvider(new FileTreeLabelProvider());
tv.setInput(new File("C:"));

And run it. Note the change to the table.

It is also possible to use the label provider to display images (eg: folders), check out the API for more info on this.

The Table Viewer

The table viewer is going to display the files for the directory we're currently viewing.  Once again, we're going to need a content provider and a label provider.

Table Content Provider

The content provider for the table is much like what we implemented for the tree.  Infact, the interface we need to implement is actually a super interface of what we use for the tree content provider.

So add the following to the project:

public class FileTableContentProvider implements IStructuredContentProvider
{
      public Object getElements(Object element)
      {
         Object kids = null;
         kids = ((File) element).listFiles();
         return kids == null ? new Object\[0\] : kids;
      }
}

Once again, this tells the table what data we want to display based on our input.
At this point, we've actually done enough to tie together the tree and table viewers.  See the Linking section below.

Table Label Provider

A singular column

Initially we just want to display the name of the file, without being fully qualified.  As such we can use the following for our label provider:

public class FileTableLabelProvider implements ITableLabelProvider
{
      public String getColumnText(Object obj, int i)
      {
        return ((File) obj).getName();
      }
}

This simply tells the viewer to use the name of the file for the label.

Providing information for other columns

Assuming that we've added additional columns to the table (see below), we need to change the Label Provider so that it can make use of these tables. For this example, we'll add the appropriate information for a size column.

public String getColumnText(Object obj, int i)
{
         switch (i)
         {
            case 0:
               return ((File) obj).getName();
                           case 1:
              return ((File) obj).getSize();
         }
         return "";
}

However, as stated, you will need to add additional columns to the model for this to work.

Additional columns

Assuming that you've followed the example this far, you will need to add additional columns to the table.  This is not technically done through the table viewer, but on the table itself.

A Note about the Table Model

The table viewer uses a custom table model behind the table component.  Generally this won't effect you in any way, however it is important to be aware of it, and what purpose it serves.

The <code>TableViewerModel</code> class acts much like a standard table model - except for the following rules:
* If no columns have been defined, it will provide one by default, with no header
* If you define your own columns, the default column disappears (ie: as if there were no columns in the model at all)

As such, if you're going to add the columns, don't expect the default one to still be there.

Add the extra columns

In your code add the following:

// Specify we want a new column with the index '''0'''
TableColumn column = new TableColumn(0);

// Specify the approprate name and size for this column
column.setHeaderValue("Name");
column.setWidth(new Extent(200, Extent.PX));

// Add the columns to the column model
tbv.getTable().getColumnModel().addColumn(column);

// Wash, Rinse and Repeat for "Size"
column = new TableColumn(1);
column.setHeaderValue("Size");
column.setWidth(new Extent(100, Extent.PX));
tbv.getTable().getColumnModel().addColumn(column);

The table viewer will automatically identify that there are more columns in the model, and populate them appropriately based on what you've told it to do in your Label Provider

Linking the Tree and Table together

A note about selection listeners

As the viewers follow the JFACE api, you gain pretty much the same listener power as you would with a standard SWT application.  Be aware however, not all the listeners may currently be implemented (in accordance with the api).  If you notice something wrong, lodge a bug.

Add Selection Listener to the Tree

To add the selection listener for this example, we don't actually need to worry about the tree object at all.  Once again, we can do it all through the viewer.

To do this, we're going to use the method "addSelectionChangedListener", which requires an implementation of the ISelectionChangedListener.  Add the following code to the example :

Assumes a tree viewer "tv" and a table viewer "tbv":

tv.addSelectionChangedListener(new ISelectionChangedListener()
{
       public void selectionChanged(SelectionChangedEvent event)
       {
         // Get the selection the user has made
         IStructuredSelection selection = (IStructuredSelection) event.getSelection();
         Object selected_file = selection.getFirstElement();
         // Set the new input on the table viewer
         tbv.setInput(selected_file);       }
});

Its important to recognise one major thing about what's going on here.  The getSelection() method returns the object that we set in the content provider. It doesn't return any weird selection object, tree reference, id or any of that other crap.  As such, we can pass it straight into the table viewer to display (remembering that we told it how to handle file objects).

Finishing up

Finally, we're going to add all of this to a split pane (as unlike the Jface/SWT example, we don't have SashForm to use).

So change your code to have the following:

SplitPane sp = new SplitPane();
sp.setResizable(true);
sp.setSeparatorPosition(new Extent(300, Extent.PX));
parent.add(sp);

And add your tree and table viewers to "sp" instead of "parent".

References

The following references may also be of some use.

http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
http://www.eclipse.org/articles/Article-Table-viewer/table_viewer.html

Although not complete, the following downloads are a port of the source code given in these articles:
TreeViewer source
TableViewer source

Labels

 
(None)
  1. Sep 24

    Anonymous says:

    Is the code for these examples downloadable?

    Is the code for these examples downloadable?

  2. Nov 06

    Pete Butland says:

    I have attached ports of the examples given in the 2 Eclipse articles. Whilst th...

    I have attached ports of the examples given in the 2 Eclipse articles. Whilst the ports are not complete, it is a start and I hope to come back to them soon. Please note that these projects are for Moomba 1.0 only.

Add Comment