Java - Sorting Trees by Recursive

最近在關注如何將SWT TreeViewer進行排序的動作,發現有個網友提到這個網站有相關範例

https://www.ibm.com/developerworks/lotus/library/expeditor-swt/

查了一下Sorting trees真的有存在,而這個範例所針對的為Tree,若是Tree本身的話也許真的

就要自行去將同一層的TreeItem Array做sorting,之後再依新的順序重新建置,並且再dispose

掉舊的部分!

在這邊主要探討的是,如何改寫這個範例變成由Recursive來進行建置新的TreeItem或排序,如

此一來即使Tree example新增至多層的結構(範例為三層),也並不需要修改排序及建置的code!

相關程式說明如下:

1. Sorting.java主程式不變
2. SortTreeListener.java修改sortTree method部分
private int columnIndex = 0;
private String sortColumn = "";
private int direction = 1;
private int numOfColumns;

private void sortTree(SelectionEvent e) {
    TreeColumn column = (TreeColumn) e.widget;
    Tree tree = column.getParent();
    tree.setSortColumn(column);
    TreeColumn sortColumn = tree.getSortColumn();
    TreeColumn columns[] = tree.getColumns();
        
    this.numOfColumns = columns.length;
    this.sortIndex = this.findColumnIndex(columns, column, numOfColumns);
    this.direction = sortColumn.getText().equals(this.sortColumn) ? -1*direction : direction;
    this.sortColumn = sortColumn.getText();
        
    tree.setSortDirection(direction == 1 ? SWT.UP : SWT.DOWN);

    doSort(tree);
 }

在這method內,原則上相關的變數,如column數量及當下columnIndex是哪一個皆不變,但

這邊有新增判斷點擊header時,到底當下為1 or -1,目的乃是在排序時要進行遞增or遞減計算

而部分變數也宣告為全域變數

再來是說明新增的method - doSort來進行排序動作
private void doSort(Tree tree){
   doSort(tree, null, tree.getItems());
}

private void doSort(Object obj, TreeItem olditem,  TreeItem treeItems[]){
  boolean isTree = false;
  //1. return condition
  if(treeItems == null || (treeItems != null && treeItems.length == 0)){
     return;
  }
  //2. sorting array
  if(obj instanceof Tree || (olditem instanceof TreeItem && olditem.getExpanded())){
   Arrays.sort(treeItems, new Comparator<TreeItem>(){
    @Override
    public int compare(TreeItem o1, TreeItem o2) {
     // TODO Auto-generated method stub
     String txt1 = o1.getText(columnIndex);
     String txt2 = o2.getText(columnIndex);
     
     if(NumberUtils.isCreatable(txt1))
         return direction*((Double.parseDouble(txt1) - Double.parseDouble(txt2)) > 0 ? 1 : -1);
     else
         return direction*(txt1.compareToIgnoreCase(txt2) > 0 ? 1 : -1);
    }
   });
  }
  //3. create new TreeItem or doSort
  for(TreeItem item : treeItems){
     TreeItem nitem = null;
     if(obj instanceof TreeItem){
        nitem = new TreeItem((TreeItem)obj, SWT.NONE);
     }else if(obj instanceof Tree){
        nitem = new TreeItem((Tree)obj, SWT.NONE);
        isTree = true;
     }
     nitem.setText(this.getColumnValues(item, numOfColumns));
     doSort(nitem, item, item.getItems());
     if(isTree){
        item.dispose();
     }
  }
  if(olditem != null){
     ((TreeItem)obj).setExpanded(olditem.getExpanded());
  }
 }

首先從tree(root)進入,傳進去的第三個參數為其子項目(TreeItem[]),此時會先判斷TreeItem[]

本身是否存在及是否擁有子項目,若沒有的話則進行return


再來是進行排序TreeItem[]的動作,在Comparator內重新定義compare method規則,判斷這兩

筆TreeItem的text為String或double來對應計算。特別要注意的是olditem變數乃是判斷,假設

- node1
    + node1_1
    -  node1_2
       - node1_2_1
       - node1_2_2

node1_1本身當下是沒有被expanded的,那麼即使它可能有子項目也不針對它們進行排序的

動作!  但反之,如果像node1_2的話就會針對他的children進行排序了!


最後,就是進行TreeItem[]的走訪,目的是建立新的TreeItem[0] or TreeItem[1]...等等

如此一來在tree的第一層節點會重新排列,並且重新setText的節點名稱

但在第一層的第一個節點建立好之後,我們並不知道它是否還有children,因此再次進入

doSort method,並且傳入的參數分別為(新節點, 舊節點, 舊節點的children array)

此時會再次回到doSort的開頭....又重複了上面的說明

這邊要特別講的是帶入舊節點的目的 - oldItem

因為新節點當下還沒有綁定任何的children,因此只能透過舊節點得知是否有被expanded

而最後哪時要做dispose的動作,這得當所有的項目都建立好之後,才可以進行,否則項目

該有的children會消失,在還沒有建立完新節點之前! 因此時機點是在進入doSort的第一個

參數為Tree的當下!

DEMO

當下先展開TreeItem1,點擊TreeColumn1後進行由下往上遞減的排序


再來多展開TreeItem0,點擊TreeColumn1後進行由上往下遞減的排序


總結

1. 程式中的部分method為沿用本來版本的code,因此在這邊不列出
如:findColumnIndex, getColumnValues
2. 本來的範例只會進行第一層項目的排序,在此加入子項目也會一起排序,前提是它也有
被展開的情形下!

留言