Saturday 26 February 2011

Eclipse TreeView generic interfaces

Tree views in Eclipse plug-ins provide much elegant ways to present the recursive data. Perhaps they handle the parent-child relationships among the nodes more intuitively than any other view representation. When I wanted to put some 'kind' of model objects as nodes in a tree view, I wanted to create a small reusable group of classes which will make the process of putting an object in a tree little simpler. Though it is not of any design pattern kind of stuff, I have benefitted rather significantly from this simple interface layer later.

This interface layer constitutes of two interfaces, TreeNode and TreeInput representing each node and the input to the tree respectively. TreeNode contract makes it possible for the tree viewer with all the data it requires such as name of the node, any associated image and whether it has any child nodes or not. The TreeInput contract represents the input to the tree viewer. Following is the complete source code of these two main contracts.

TreeNode.java

import org.eclipse.swt.graphics.Image;



public interface TreeNode {



 // Returns name of the node

 public String getText();



 // Returns image of the node

 public Image getImage();



 // Returns if this node has any child or not

 public boolean hasChildren();



 // Returns it's child nodes

 public TreeNode[] getChildren();



 // Returns it's parent node

 public TreeNode getParent();

}

TreeInput.java

import java.util.List;



public interface TreeInput {

 

 // Returns the list of top level nodes as input to the tree viewer

 public List<TreeNode> getInput(); 



}



Having set the stage, let's take a use case as we want to create a simple tree view for a file system. That essentially two types nodes: directory and file. The root of the tree is the root of the file system (or any relative location) from where the top level directories, with no parent come as nodes. Only directory type nodes can have children nodes which are again either directory nodes or file nodes. And, let's consider someone called FileViewInputProvider is what the tree view looks for input which actually provides all the top level nodes through TreeInput.

Content and display to a tree viewer can be controlled by ITreeContentProvider and LabelProvider. A generic implementation to the content provider returns nodes through the callbacks. For example, the hasChildren() method checks the TreeNode to see whether or not it has child nodes. The getChildren(Object obj) is called when a tree node is expanded at the view which actually gets the child nodes from the TreeNode#getChildren().

TreeContentProvider.java

import java.util.List;



import org.eclipse.jface.viewers.ITreeContentProvider;

import org.eclipse.jface.viewers.Viewer;



public class TreeContentProvider implements ITreeContentProvider {



 @Override

 public Object[] getChildren(Object parentElement) {

  if (parentElement instanceof TreeNode) {

   return ((TreeNode) parentElement).getChildren();

  } 

  if (parentElement instanceof TreeInput) {

   return getElements(parentElement);

  }

  return new Object[0];

 }



 @Override

 public Object getParent(Object element) {

  if (element instanceof TreeNode) {

   return ((TreeNode) element).getParent();

  }

  return null;

 }



 @Override

 public boolean hasChildren(Object element) {

  if (element instanceof TreeNode) {

   return ((TreeNode) element).hasChildren();

  }

  return false;

 }



 @Override

 public Object[] getElements(Object inputElement) {

  if (inputElement instanceof TreeInput) {

   List<TreeNode> input = ((TreeInput)inputElement).getInput();

   return input.toArray();

  }

  return new Object[0];

 }



 @Override

 public void dispose() {

  // Do nothing. Subclasses may override.

 }



 @Override

 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

  // Do nothing. Subclasses may override.

 }



}



Label provider is called once the content is set and ready to display in the viewer. It essentially provides a name to the  node and an associated image. The generic label provider TreeLableProvider extends LabelProvider and overrides the getText(Object obj) and getImage(Object obj) methods. For these data, the label provider rely on the TreeNode contract.

TreeLabelProvider.java

import org.eclipse.jface.viewers.LabelProvider;

import org.eclipse.swt.graphics.Image;



public class TreeLabelProvider extends LabelProvider {



 /**

  * Returns the text to be displayed in the tree view

  */

 public String getText(Object obj) {

  if (obj instanceof TreeNode) {

   return ((TreeNode) obj).getText();

  }

  return (obj != null) ? obj.toString() : "???";

 }



 /**

  * Returns the image to be displayed in the tree view

  */

 public Image getImage(Object obj) {

  if (obj instanceof TreeNode) {

   return ((TreeNode) obj).getImage();

  }

  return null;

 }



}



These simple interfaces would help creating tree views quickly by providing the required contract between the Eclipse tree viewer objects and the node input objects. This reusability provides speed and less maintenance while working with Eclipse tree views.