给TableViewer,TreeViewer,GridTableViewer和GridTreeViewer添加排序功能

By | 7月 3, 2018

我们的需求是:column第一次被点击,按这列升序排列,最小的在第一行,column head显示一个V(linux是实心倒三角);再次点击,按此列降序排列,最大的在第一行,colum head显示倒V(linux是实心三角)。

通常情况下,大多数table或tree都需要排序功能。我们以TableViewer为例,先了解下如果给viewer添加排序功能,其它viewer都类似。添加排序功能的步骤如下:

1 实现一个ViewerComparator

默认的ViewerComparator是无法排序的,compare时要根据当前点击的column和排序方向来比较。

class MyViewerComparator extends ViewerComparator {
  @Override
  public int compare(Viewer viewer, Object element1, Object element2) {
    // 1. Get sortColumnIndex and sortDirection
    int sortColumnIndex = ...;
    int sortDirection = ...;

    // 2. Get the text of element in sort column.
    String text1 = ...;
    String text2 = ...;

    // Compare text1 with text2.
    int compareResult = ...;
    return SWT.DOWN == sortDirection ? compareResult : -CompareResult;
  }
}

compare里的三个步骤都有坑:

  1. JFace Table和JFace Tree有table.indexof(table.getSortColumn())和table.getSortDirection(),来得到sortColumnIndex和sortDirection;但是Nebula Grid没有,只有GridColumn—>getSort()方法。
  2. 获得element text其实就是获得此元素在该columnIndex的值,通常会把ColumnLabelProvider里的代码copy过来,通过switch组合获得该column的text。
  3. 比较text值,是按照字符串比较,日期比较还是数值比较等。

2 给Column添加点击事件

第一次点击升序排列,第二次点击降序排列。

column.addSelectionListener(new SortSelectionAdapter() {
  @Override
  public void widgetSelected(SelectionEvent e) {
    Table table = viewer.getTable();
    TableColumn eventColumn = (TableColumn) e.widget;
    if (table.getSortColumn() == eventColumn) {
      table.setSortDirection(SWT.DOWN != table.getSortDirection() ? SWT.DOWN : SWT.UP);
    } else {
      table.setSortColumn(eventColumn);
      table.setSortDirection(SWT.DOWN);
    }
    viewer.refresh();
  }
});

这里也有差异:

  1. JFace Table和JFace Tree通过setSortColumn(Column)和setSortDirection(int)来设置排序列和方向。但是Grid只能通过GridColumn—>setSort(int),设置排序方法。
  2. Table和Tree只能对一列进行排序,Grid可以对多列设置排序,为了保持一致,需要将此列以外的其它列sortDirection设置为SWT.NONE。

3 每次都要实现(繁琐)

不管是TableViewer,TreeViewer,GridTableViewer还是GridTreeViewer,使用步骤都一样。但是每次都得创造一个新的ViewerComparator类,然后实现其compare方法。分析一下,我们发现我们可以只有一个ViewerComparator实例,支持所有viewer的排序功能。

  1. 获得sortColumnInde和sortDirection,可以封装成一个工具类叫ViewerSortFactor,通过它来获得sortColumnIndex和sortDirection。
  2. 获得element text:
    • Step 1: 如果当前列有ColumnLabelProvider,则通过其获得text。如果text是null或empty,则进入step 2。
    • Step 2: 如果viewer拥有ITableLabelProder,则通过getColumnIndex(element, column)获得text。如果text是null或empty,则进入step 3。
    • Step 3: 通过反射调用super method”getLabel”获得text。
  3. 比较text,根据值的类型,选取适当的比较方法。

给Column添加点击事件,我们也可以封装成一个util。ViewerComparatorUtil—>addListener(Viewer):给任何Viewer的column添加点击事件,如果column已经添加过了,就不再添加。因此该方法可以调用多次,只要有新column生成,就调用此方法便可,不用专门给column一个一个加listener。

4 提取成工具类

image

以TableViewer为例,添加排序功能,只需要两步。TreeViewer,GridTableViewer和GridTreeViewer的添加排序功能,完全一模一样。

TableViewer tableViewer = ...;
tableViewer.setComparator(new ViewerComparatorAllInOne());
// Add columns ...
ViewerComparatorUtil.addListener(tableViewer);

5 ViewerComparatorAllInOne说明

它的比较规则是:

  1. Step 1:查看元素是否属于同一个category,如果是同一个category,进入step 2。默认都是同一个category,用户可以override category(Element, SortDir)来实现分组。
  2. Step 2:如果都是Double值,按double比较,否则进入step 3。
  3. Step 3:如果都是Date值,按Date比较,否则进入step 4。有参构造函数,传入SimpleDateFormat,会尝试查看是否是Date。如果构造时没有传入一个SimpleDateFormat,Date将会按照字符串排序。
  4. Step 4:按照字符串的字母数字顺序,AlphanumComparator是从网上下载的。

核心比较代码:

private int toCompareResult(ViewerSortFactor sortFactor, int result) {
  return SWT.DOWN == sortFactor.getSortDir() ? result : -result;
}

@Override
public int compare(Viewer viewer, Object element1, Object element2) {
  if (!(viewer instanceof ColumnViewer)) {
    return 0;
  }

  ViewerSortFactor sortFactor = ViewerSortFactor.get(viewer);
  if (sortFactor.getColumnIndex() < 0 || sortFactor.getSortDir() == SWT.NONE) {
    return 0;
  }

  int cat1 = category(element1);
  int cat2 = category(element2);
  if (cat1 == cat2) {
    cat1 = category(element1, sortFactor.getSortDir());
    cat2 = category(element2, sortFactor.getSortDir());
  }

  if (cat1 != cat2) {
    return toCompareResult(sortFactor, cat1 - cat2);
  }

  String text1 = getElementText((ColumnViewer) viewer, element1, sortFactor.getColumnIndex());
  String text2 = getElementText((ColumnViewer) viewer, element2, sortFactor.getColumnIndex());

  Double value1 = Doubles.tryParse(text1);
  Double value2 = Doubles.tryParse(text2);
  if (value1 != null && value2 != null) {
    return toCompareResult(sortFactor, Double.compare(value1, value2));
  }

  Date date1 = getDate(text1);
  Date date2 = getDate(text2);
  if (date1 != null && date2 != null) {
    return toCompareResult(sortFactor, date1.compareTo(date2));
  }

  return toCompareResult(sortFactor, ((AlphanumComparator) getComparator()).compare(text1, text2));
}

6 源文件

三个工具类的源文件下载: