ULC Filtered Table Model

Purposes

This contribution serves two purposes. It offers a Filtered Table Model and a Sorted Table Model.

ULCXTable of the ULCSwingX project has integrated the implementation of the sorted and filtered table model and offers the corresponding API.

Filtered Table Model

Many ULC applications display some table data that must be filterable according to user criteria. This contribution provides a generic filtered table model and a set of generic filters (date, boolean, string, substring, not, and, or).

How to use

In order to make our table data filterable we decorate it with a FilteredTableModel and assign the latter to an instance of ULCTable.

ITableModel tableModel = new DefaultTableModel(rows, columnNames);
FilteredTableModel filteredTableModel = new FilteredTableModel(tableModel);
ULCTable table = new ULCTable(filteredTableModel);

When the user chooses a filter criteria, we set a corresponding instance of IRowFilter on our FilteredTableModel. In the example below, we filter for all rows that contain the string hello (case-insensitive) in the second column and that also contain the substring ULC (case-sensitive) in the third column.

RowFilters.AndRowFilter andFilter = new RowFilters.AndRowFilter();
andFilter.add(new RowFilters.StringRowFilter(2, "hello", true));
andFilter.add(new RowFilters.SubstringRowFilter(3, "ULC", false));
filteredTableModel.setFilter(andFilter);

We can combine FilteredTableModel and SortedTableModel, as shown in the example below.

ITableModel tableModel = new DefaultTableModel(rows, columnNames);
SortedTableModel sortedTableModel = new SortedTableModel(tableModel);
FilteredTableModel filteredTableModel = new FilteredTableModel(sortedTableModel);
ULCTable table = new ULCTable(filteredTableModel);

How it is implemented

The FilteredTableModel is a subclass of DecoratedTableModel and provides a public method setFilter() to set an instance of IRowFilter. The IRowFilter decides for each row if the contained data matches the filter criteria. The FilteredTableModel is registered with the underlying table model as a table listener. Therefore, we can modify the underlying table model data and the changes will be reflected in the FilteredTableModel.

public class FilteredTableModel extends DecoratedTableModel

{
  private IRowFilter fFilter;
  public FilteredTableModel(ITableModel sourceModel)
  {
    super(sourceModel);
    fFilter = null;
    dataChanged();
  }

  public void setFilter(IRowFilter filter)
  {
    if((fFilter == null && filter != null) || (fFilter != null && !fFilter.equals(filter)))
    {

      fFilter = filter;
      dataChanged();
    }
  }
  ...
  // listen to events from the underlying table model
  public void tableChanged(TableModelEvent event)
  {
    switch(event.getType())
    {

      case TableModelEvent.UPDATE:
        handleRowsUpdated(event);
        break;
      case TableModelEvent.INSERT:
        handleRowsInserted(event);
        break;
      case TableModelEvent.DELETE:
        handleRowsDeleted(event);
        break;
      default:
        throw new IllegalArgumentException("Unknown event type: " + event.getType());
    }

  }
  …
}

Sorted Table Model

Most ULC applications have some table data that must be displayed in a sorted way. This contribution provides a generic sorted table model and a generic controller to change sequence and direction of sorting. A visual indication of the sorting state is provided on the table header.

By default, the sorted table model sorts the table data according to the natural ordering of its elements. As an alternative, custom comparators can be supplied.

How to use

In order to make our table data sortable we decorate it with a SortedTableModel and assign the latter to an instance of ULCTable.

ITableModel tableModel = new DefaultTableModel(rows, columnNames);
SortedTableModel sortedTableModel = new SortedTableModel(tableModel);
ULCTable table = new ULCTable(sortedTableModel);

We now associate an action listener with the table header in order to be notified when the user clicks on a table column header. The table data is sorted according to the selected column and an icon is displayed in the table column header signaling the sorted column and its direction of sorting.

SortedTableModel sortedTableModel = (SortedTableModel) table.getModel();
ULCListSelectionModel selectionModel = table.getSelectionModel();
table.getTableHeader().addActionListener(new SortTableColumnHandler(sortedTableModel, selectionModel));

We can combine SortedTableModel and FilteredTableModel, as shown in the example below.

ITableModel tableModel = new DefaultTableModel(rows, columnNames);
FilteredTableModel filteredTableModel = new FilteredTableModel(tableModel);
SortedTableModel sortedTableModel = new SortedTableModel(filteredTableModel);
ULCTable table = new ULCTable(sortedTableModel);

How it is implemented

The SortedTableModel is a subclass of DecoratedTableModel and provides public methods to define sequence and direction of sorting. The SortedTableModel is registered with the underlying table model as a table listener. Therefore, we can modify the underlying table model data and the changes will be reflected in the SortedTableModel.

public class SortedTableModel extends DecoratedTableModel

{
  private TableModelRowComparator fComparator;
  private int[] fSortedColumns;
  private boolean fAscending;
  public SortedTableModel(ITableModel sourceModel)
  {

    this(sourceModel, new Comparator[0]);
  }
  public SortedTableModel(ITableModel sourceModel, Comparator[] comparators)
  {
    super(sourceModel);
    fComparator = new TableModelRowComparator(comparators);
    fSortedColumns = new int[0];
    fAscending = true;
    dataChanged();
  }

  ...
  public void sort(int[] sortedColumns)
  {
    if(!Arrays.equals(fSortedColumns, sortedColumns))
    {
      fSortedColumns = sortedColumns;
      dataChanged();
    }

  }
  public void sort(boolean ascending)
  {
    if(fAscending != ascending)
    {
      fAscending = ascending;
      dataChanged();
    }

  }
  ...
  // listen to events from the underlying table model
  public void tableChanged(TableModelEvent event)
  {
    switch(event.getType())
    {
      case TableModelEvent.UPDATE:
        handleRowsUpdated(event);
        break;
      case TableModelEvent.INSERT:
        handleRowsInserted(event);
        break;
      case TableModelEvent.DELETE:
        handleRowsDeleted(event);
        break;
      default:
        throw new IllegalArgumentException("Unknown event type: " + event.getType());
    }

  }
  …
}

Resources