JFace TreeViewer 排序 - 更新

JFace TreeViewer Sorting - Updated

链接进来,我问 @greg-449 为什么我的比较器不起作用(就像那个页面上的问题)我只是想按标签提供商排序。

我的 TreeViewer 在我的应用程序中使用了一些通用功能进行了一些扩展。然后有 3 个派生扩展它,但本质上下面的例子是一个抽象 class 是因为它不打算直接实例化,其余的 Eclipse 插件代码必须为各种视图选择一个具体的实现部分。但是,我需要所有这些中的排序功能,所以这可能是它应该去的地方。

我对下面的代码进行了匿名处理,但它本质上是 TreeViewer 的抽象包装器,在所有派生中都有 3 列。第一列(索引 0)始终是某种类型的树,因此节点是可扩展的,这会在第二列和第三列(索引 1 和 2)中产生更多可见的数据行。这些列仅包含文本数据。

因此,我希望实现的是一个可排序的控件,单击索引为 0 的 header 列将清除所有排序并呈现最初加载的数据,同时单击任何其他 headers 将执行以下操作:

  1. 如果还不是排序列,则按升序 (UP) 排序
  2. 如果重复单击同一排序列,则反转排序方向

这是我尝试过的方法,从 link 中提到的 ViewerComparator class 开始 post:

    public abstract class MyTreeViewer extends TreeViewer {
    
        public static final int ACTIVE_TOTAL_COLUMN_WIDTH = 120;
        public static final int FIRST_COLUMN_WIDTH = 180;
    
        private static final String SPECIAL_FIELD = "specialField";
        private TreeColumn sortColumn = null;
    
        final RMService rmService;
        final PService pService;
    
        public MyTreeViewer(Composite parent) {
    
            this(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION);
        }
    
        public MyTreeViewer(Composite parent, int style) {
    
            super(parent, style);
    
            this.rmService = UIActivator.getDefault().getInjector().getInstance(RMService.class);
            this.pService = UIActivator.getDefault().getInjector().getInstance(PService.class);
    
            this.setUseHashlookup(true);
    
            this.addSelectionChangedListener(new ISelectionChangedListener() {
    
                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    if (!event.getSelection().isEmpty()) {
                        Tree tree = getTree();
                        if (tree != null) {
                            List<MyTreeItem> treeItems = Lists.newArrayList();
                            for (int i = 0; i < tree.getSelectionCount(); i++) {
                                TreeItem item = tree.getSelection()[i];
                                Object obj = item.getData();
                                if (obj instanceof MyTreeItem) {
                                    treeItems.add((MyTreeItem) obj);
                                }
                            }
                            handleSelectionChanged(treeItems);
                        }
                    }
                }
            });
    
            this.setComparator(new ViewerComparator());
        }
    
        protected abstract void handleSelectionChanged(Collection<MyTreeItem> treeItemList);
    
        public void initTree(List<ViewField> fields, IMemento memento) {
    
            TableLayout tableLayout = new TableLayout();
            Tree tree = getTree();
    
            for (ViewField field : fields) {
    
                TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
                if (SystemPropertiesLoader.OPERATING_SYSTEM_NAME.equalsIgnoreCase(IConstants.LINUX_OS)) {
                    column.getColumn().setResizable(false);
                    column.getColumn().setMoveable(false);
                } else {
                    column.getColumn().setResizable(true);
                    column.getColumn().setMoveable(true);
                }
                column.getColumn().setData(SPECIAL_FIELD, field);
                column.getColumn().setText(field.getFieldName());
    
                tableLayout.addColumnData(new ColumnPixelData(field.getWidth(), false));
    
                column.getColumn().addSelectionListener(new SelectionListener() {
                    @Override
                    public void widgetSelected(SelectionEvent selectionEvent) {
                        if (selectionEvent.getSource() instanceof TreeColumn) {
                            TreeColumn column = (TreeColumn)selectionEvent.getSource();
                            Tree tree = getTree();
                            if (column.getText().equalsIgnoreCase("") && sortColumn != null) {
                                // file column clicked - use it to clear any sort order
                                sortColumn = null;
                                tree.setSortColumn(sortColumn);
                                tree.setSortDirection(0);
                                refresh();
                            } else {
                                sortColumn = column;
                                tree.setSortColumn(sortColumn);
                                tree.setSortDirection(invertSortDirection(tree.getSortDirection()));
                                refresh();
                            }
                        }
                    }
    
                    @Override
                    public void widgetDefaultSelected(SelectionEvent selectionEvent) {
                        // not currently used, but required as part of implementation of SelectionListener interface
                    }
                });
            }
            tree.setLayout(tableLayout);
            tree.setLinesVisible(false);
            tree.setHeaderVisible(true);
            tree.layout(true);
    
            // prevent expanding/collapsing tree item on dbl click
            tree.addListener(SWT.MeasureItem, new Listener() {
    
                @Override
                public void handleEvent(Event event) {
                    // nothing
                }
            });
        }
    
        private int invertSortDirection(int sortDirection) {
            if (sortDirection != SWT.UP && sortDirection != SWT.DOWN) {
                return SWT.UP;
            } else if (sortDirection == SWT.UP) {
                return SWT.DOWN;
            }
            return SWT.UP;
        }
    
        @Override
        public void refresh() {
            super.refresh();
        }
    
        @Override
        public void refresh(boolean updateLabels) {
            super.refresh(updateLabels);
        }
    }

我继承了这段代码,所以有一些特殊的东西修复了错误,我不会在不知道它不会在 QA 中产生回归的情况下接触,例如防止 expanding/collapsing 树项的粗略方法on double-click 已实施。事实上,到目前为止,我修改的这个特定代码的唯一部分是插入 addSelectionListener 闭包来处理列 header 的点击,以及 invertSortDirection 方法。

当我 运行 并单击 header 时发生的情况与我预期的一样。我看到 UI 插入符号指示列索引 1 或 2 上的排序方向,但我没有看到数据已排序。单击列索引 0 的 header 将清除排序列和顺序。如果数据已排序,我希望查看器在 UI 中刷新到其原始加载状态,然后再请求任何列排序。

关于前面的问题(link 编在顶部),我的解释是如果需要按标签文本排序,我应该只添加 this.setComparator(new ViewerComparator()); 行。如果我必须编写扩展 ViewComparator.

的 class,我不知道我会覆盖或更改什么

None 从上面的代码派生的 classes 在列上实现了一个侦听器。我可以跟踪代码,上面处理 header 点击的代码确实执行了。

那么,我是否需要扩展 ViewComparator?如果需要,我应该更改哪些内容才能获得所需的行为?

(我也可以取消 TreeColumn sortColumn 字段,因为树本身 'remembers' 这个。Google Guice 注入服务被这个抽象树查看器的派生使用)

更新 1:

我的意图是展示上面定义的这个通用查看器的派生 class,但在我检查之后,很明显它对当前为什么没有发生排序的问题没有多大用处。

我发现我认为 'smoking gun' 为什么自定义标签提供程序本身没有从我继承项目的一位前任那里进行排序。这是标签提供者:

public class MyCustomLabelProvider extends LabelProvider implements ITableLabelProvider {

    final IDecoratorManager decorator;

    public MyCustomLabelProvider() {
        decorator = PlatformUI.getWorkbench().getDecoratorManager();
    }

    @Override
    public Image getImage(Object element) {
        return super.getImage(element);
    }

    @Override
    public String getText(Object element) {
        return null;
    }

    @Override
    public Image getColumnImage(Object element, int columnIndex) {
        if (element instanceof MyTreeItem) {
            if (columnIndex == 0) {
                final MyTreeItem item = (MyTreeItem)element;
                switch (item.getType()) {
                    // snipped cases returning different images
                }
            }
        }
        return null;
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {
        if (element instanceof MyTreeItem) {
            return showColumns((MyTreeItem) element, columnIndex);
        }
        return null;
    }

    private String showColumns(MyTreeItem element, int columnIndex) {
        if (columnIndex == 0) {
            return element.getName();
        }
        if (columnIndex == 1) {
            return String.valueOf(element.getCustomProperty1());
        }
        if (columnIndex == 2) {
            return String.valueOf(element.getCustomProperty2());
        }
        return "";
    }
}

通过跟踪 ViewerComparator 代码,程序最终调用 getText,它始终返回 null

ViewerComparator 只是试图抓取标签文本,由于上面的原因是 null,它被修改为一个空字符串。然后它使用 Java String compareTo 方法进行比较。由于两者都是"",因此没有比较结果表明需要交换元素顺序。

我想知道如何更改 getText 方法以某种方式获取单击列的原始列索引,并在其中包含逻辑来确定从我的基础数据 [=110] 中读取哪个 属性 =] 用于在查看器中填充一行。对我来说,这是行不通的,因为我使用的基础 object 具有 non-String 用于填充 3 列中的 2 列的属性。

用户 greg-449 在评论中指出我需要扩展和覆盖 ViewerComparator 来制作我自己的版本,但直到我走到这一步,或者直到他说 ...

The standard ViewerComparator only supports one column

...原因还不清楚。最初 linked post 没有那个说明。或者至少,在撰写本文时不是。

在他提到那一点时,我并没有表示问题已解决,只是我认为我已经找到了问题。我只是从 ViewerComparator 开始,跟踪 运行ning 代码,发现现有比较器无法 re-order 项目的原因,假设一旦我更新,它可能会结束代码

我想进一步了解 greg-449 所说的,即使你只有一个列,默认的 ViewerComparator 也不支持比较 属性 的元素基础数据 object 不是 Java 字符串 。您需要为此实现自己的比较器。因此很清楚为什么 greg-449 建议在我一开始就说我有 3 列 - 但我仍然感到困惑,因为我只是认为我在我想要排序的 2 列上有文本数据,即使该文本实际上已转换来自 int。我只是觉得我读的 linked post 可能很贴切,因为它提到了对文本进行排序,而没有提到单列的限制,或者 Java 字符串数据类型。

所以继续前进,我现在已经让我的代码按我想要的方式工作了。更重要的是,我不必更改 getText 方法,因为这是默认情况下专门调用的 ViewerComparator 并且我找到了一种方法来遵循不需要它的地方。我将 post 在回答中包含这些内容。

所以除了我在问题中的更新...

我试图找到 greg-449 引用的不同比较器的示例,但是如果我尝试导入它,特定的包在我的环境中不可用。

然后我搜索了一种编写自定义比较器的方法并找到了 Vogella post: https://www.vogella.com/tutorials/EclipseJFaceTable/article.html#sort-content-of-table-columns

我注意到我上面提到的列索引问题是通过在 Vogella 代码中为每列创建一个处理程序来处理的。我主要是复制它,对它执行比较的方式稍作更改以满足我的要求:

public class MyCustomViewerComparator extends ViewerComparator {
    private int propertyIndex;
    private static final int DESCENDING = 1;
    private int direction;

    public MyCustomViewerComparator() {
        this.propertyIndex = 0;
        direction = DESCENDING;
    }

    public int getDirection() {
        return direction == 1 ? SWT.DOWN : SWT.UP;
    }

    public void setColumn(int column) {
        if (column == this.propertyIndex) {
            // Same column as last sort; toggle the direction
            direction = 1 - direction;
        } else {
            // New column; do an ascending sort
            this.propertyIndex = column;
            direction = DESCENDING;
        }
    }

    @Override
    public int compare(Viewer viewer, Object e1, Object e2) {
        MyTreeItem p1 = (MyTreeItem) e1;
        MyTreeItem p2 = (MyTreeItem) e2;
        int rc = 0;
        switch (propertyIndex) {
            case 1:
                rc = ( p1.getCustomProperty1() == p2.getCustomProperty1() ? 0 : p1.getCustomProperty1() - p2.getCustomProperty1() );
                break;
            case 2:
                rc = ( p1.getCustomProperty2() == p2.getCustomProperty2() ? 0 : p1.getCustomProperty2() - p2.getCustomProperty2() );
                break;
            default:
                rc = 0;
        }
        // If descending order, flip the direction
        if (direction == DESCENDING) {
            rc = -rc;
        }
        return rc;
    }
}

可以看出,我们有一个标准的compare函数,return是一个负数,匹配时为零,或者是一个正数,表示两个项目应该处于哪个顺序。或者正如标准 JavaDoc 所述:

the value {@code 0} if the argument string is equal to * this string; a value less than {@code 0} if this string * is lexicographically less than the string argument; and a * value greater than {@code 0} if this string is * lexicographically greater than the string argument.

就在我的版本中,compare 正在使用我的数据 object 中的 int 值。

除此之外,如果用户单击默认为列索引 0 的列的 header 列,我希望它清除任何排序并 return 格式查看器最初加载为。

为了实现这一点,我简单地更改了我最初在 MyTreeViewer 中使用的 SelectionListener (请参阅有问题的第二个代码清单) 每列 ,并且使其使用 Vogella 示例中的以下内容(再次调整):

    private SelectionAdapter getSelectionAdapter(final TreeColumn column,
                                                 final int index) {
        SelectionAdapter selectionAdapter = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                comparator.setColumn(index);
                if (index == 0) {
                    viewer.getTree().setSortDirection(0);
                    viewer.getTree().setSortColumn(null);
                } else {
                    int dir = comparator.getDirection();
                    viewer.getTree().setSortDirection(dir);
                    viewer.getTree().setSortColumn(column);
                }
                viewer.refresh();
            }
        };
        return selectionAdapter;
    }

上面的 if (index == 0) 检查,如果为 true,则为我清除排序,否则在已单击的任何其他列 header 上排序。由于列索引与列的设置方式有关,因此它们不受 UI 中具有可移动列的影响。您可以更改可见列的顺序而不会产生有害影响。

据我所知,只有用于比较元素的逻辑才是有用的,并且需要在自定义比较器中覆盖。除此之外,唯一要认识到的是您希望比较器专门连接到每一列,方法是实现 SelectionAdapter 接口并将其添加到列中,同时还能够跟踪列索引。 Vogella link 确实显示了这一点。基本上你会在比较器上看到存在一个 setColumn 方法,它在 SelectionAdapter 中用于设置要在 compare.

中测试的列索引