Filtering tables in SWT/JFace

October 26, 2012 | 3 min Read

Working with tables or trees with more than a handful of rows, you quickly find that you need a way to filter or search for content, otherwise they become unusable. The Eclipse workbench offers an out-of-the-box component named FilteredTree which adds an input field to an SWT tree where filter strings can be entered by the user. However, support for SWT tables is missing. This post shows how filter support can be added to tables using the existing capabilities in the workbench.

Convert the table (viewer) to a tree (viewer)

The SWT Tree is a very powerful widget. Apart from the arrangement of data in a tree hierarchy, it supports multi-column display of tree items. Since the APIs of Tree(Viewer) and Table(Viewer) are quite similar, it is straightforward to convert your Table into a Tree.

Wrap the tree into a FilteredTree

Once your code uses a Tree or a TreeViewer, it can easily be wrapped into a FilteredTree:

    FilteredTree filteredTree = new FilteredTree(parent, SWT.BORDER, new PatternFilter(), true);
    TreeViewer treeViewer = filteredTree.getViewer();

The PatternFilter parameter represents the algorithm that is applied to find matches for a given filter string.

Adapt the PatternFilter

Unfortunately, we’re not quite done yet. The default PatternFilter is designed to search for matches in the text returned by the TreeViewer’s label provider:

    protected boolean isLeafMatch(Viewer viewer, Object element){
        String labelText = ((ILabelProvider) ((StructuredViewer) viewer).getLabelProvider()).getText(element);
        if(labelText == null) {
            return false;
 }
        return wordMatches(labelText);  
    }

Obviously this cannot work for column-based TreeViewers since there is no single textual representation of an element but rather a text for each column.

If you are using TreeViewerColumns to define the columns of your tree, the PatternFilter will need to request the ColumnLabelProviders for each of the single columns. To do this, simply subclass PatternFilter and override isLeafMatch:

    protected boolean isLeafMatch(final Viewer viewer, final Object element) {
        TreeViewer treeViewer = (TreeViewer)viewer;
        int numberOfColumns = treeViewer.getTree().getColumnCount();
        boolean isMatch = false;
        for (int columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) {
            ColumnLabelProvider labelProvider = (ColumnLabelProvider)treeViewer.getLabelProvider(columnIndex);
            String labelText = labelProvider.getText(element);
            isMatch |= wordMatches(labelText);
        }
        return isMatch;
    }

If you are using TreeColumns to define the columns of your tree, the TreeViewer will need to have an ITableLabelProvider. The isLeafMatch method can then be adapted to loop over all columns :

    protected boolean isLeafMatch(final Viewer viewer, final Object element) {
        TreeViewer treeViewer = (TreeViewer)viewer;
        int numberOfColumns = treeViewer.getTree().getColumnCount();
        ITableLabelProvider labelProvider = (ITableLabelProvider)treeViewer.getLabelProvider();
        boolean isMatch = false;
        for (int columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) {
            String labelText = labelProvider.getColumnText(element, columnIndex);
            isMatch |= wordMatches(labelText);
        }
        return isMatch;
    }

By reusing the existing FilteredTree to filter our tables, we saved the effort to implement, test and maintain our own FilteredTable. And lazy programmers are good programmers, aren’t they?