Eclipse Plug-in - Implement TreeViewer for autoExpand and autoPack functions

這一篇將說明如何建構一TreeViewer UI,當點擊父節點時能夠自動pack相關文字至可顯示的

欄位大小;再來是說明如何達到autoExpand的效果,即自動指定展開哪一個父節點,可用在

初始化建立TreeViewer的當下自動展開之用。

由於展示的TreeViewer是建構在FormPage下面,因此會先說明如何設定FormPage相關的

extension,並且再追朔如何設定menu清單的項目,由此點擊item帶出頁面。

1. 建立plugin.xml的extension

建立menu



設定其extension

menu

menu內的項目名稱,綁定的是command ID所命名的名稱

command ID

設定handler,點選項目後執行的程式,由command ID串連


於點擊選項後,產生editor page,handler程式如下
public class OpenMyProjectFormHandler extends AbstractHandler{
 
 @Override
 public Object execute(ExecutionEvent event) throws ExecutionException {
  IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
  String editorId = "com.bin.practice.ui.MyFormEditor";
  try {
   NullEditorInput input = new NullEditorInput();
   IWorkbenchPage page = window.getActivePage();
   page.openEditor(input, editorId, true, IWorkbenchPage.MATCH_ID);
  } catch (PartInitException e) {
   e.printStackTrace();
  }
  return null;
 }
}


設定其extension


2. 建立主頁面

editor page (MyFormEditor.java) 程式碼如下:
public class MyFormEditor extends FormEditor{

 @Override
 protected void addPages() {
  // TODO Auto-generated method stub
  try {
   this.addPage(new MyFormPage(this));
  } catch (PartInitException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public void doSave(IProgressMonitor monitor) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void doSaveAs() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public boolean isSaveAsAllowed() {
  // TODO Auto-generated method stub
  return false;
 }

}

接下來才是實際的頁面內容(MyFormPage.java)
public class MyFormPage extends FormPage{
 private TreeViewer viewer;
 private Tree tree;
 private Map<String, File> treeMap;
 
 public MyFormPage(FormEditor editor) {
  this(editor, MyFormPage.class.getName(), "Name List");
  
 }

 protected void createFormContent(IManagedForm managedForm) {
  ScrolledForm form = managedForm.getForm();
  
  treeMap = new HashMap<String, File>();
  
  Composite formBody = form.getBody();
  Display display = formBody.getDisplay();
  
  int numColumns = 2;
  formBody.setLayout(new GridLayout(numColumns, false));
  
  viewer = new TreeViewer(formBody, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
  viewer.setContentProvider(new ViewContentProvider());
  
  createColumn("NAME1", new ViewLabelProvider(true));
  createColumn("NAME2", new ViewLabelProvider(false));

  tree = viewer.getTree(); 
  tree.setHeaderVisible(true);
  tree.setLinesVisible(true);
  
  GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
  tree.setLayoutData(gd);

  File [] res = getResource();
  viewer.setInput(res);
  
  //ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE); 
  
  //viewer.expandToLevel(treeMap.get("DEF-456-8888"), 1);
  setPack();
  viewer.addTreeListener(new ITreeViewerListener(){

   @Override
   public void treeCollapsed(TreeExpansionEvent event) {
    // TODO Auto-generated method stub
    File file = (File)event.getElement();
    System.out.println(file.getName()+"....collapsed");
    setAutoPack(display);
   }

   @Override
   public void treeExpanded(TreeExpansionEvent event) {
    // TODO Auto-generated method stub
    File file = (File)event.getElement();
    System.out.println(file.getName()+"....expanded");
    setAutoPack(display);
   }
  });
  form.pack();
 }
}

首先我們要提到的是createFormContent method,當您繼承FormPage時需定義之,您自訂的元

件可以在此建立。在此主要會以建立TreeViewer來說明之。

#設定資料來源 => setInput

在此先說明treeviewer的資料來源,當呼叫getResource時會針對Eclipse根目錄下的treeviewer

資料夾進行Filter,針對dirFilter部分若要深入到commons.properties那一層的話需要滿足在

ABC時上一層為treeviewer,而在ABC-123-321-555時上一層目錄名稱與其本身名稱開頭相等

。取得目錄的Filter之後,接下來就是過濾是否有檔案commons.properties檔案

D:\eclipse_mars\treeviewer\ABC\ABC-123-321-555\commons.properties
D:\eclipse_mars\treeviewer\DEF\DEF-456-8888\commons.properties

若皆符合的話

就會得到commons.properties本身,取其getParentFile即為

+ ABC-123-321-555
+ DEF-456-8888

再存至pFile陣列內
private File[] getResource(){
  final File directory = new File("treeviewer");
  File[] pFile = null;
  IOFileFilter dirFilter = new AbstractFileFilter() {
   @Override
   public boolean accept(File dir, String name) {
    if(name.startsWith(dir.getName())) //confirm sub-directory start group name 
     return true;
    else{
     return dir.getName().startsWith(directory.getName()) && !name.startsWith(".");
    } 
   }
  };
  
  IOFileFilter fileFilter = new NameFileFilter("commons.properties");
  
  Collection<File> filters = FileUtils.listFiles(directory, fileFilter, dirFilter);
  
  pFile = new File[filters.size()];
  
  int i = 0;
  for(File f : filters){
   treeMap.put(f.getParentFile().getName(), f.getParentFile());
   pFile[i++] = f.getParentFile();
  }
  return pFile;
 }

#建構treeviewer node => setContentProvider(new ViewContentProvider());

這個類別會進行的是將剛剛得到的資料進行某些行為定義

需要定義的method為

getElements
getChildren
getParent
hasChildren

getElements部分
一生成treeviewer時就會執行。主要為第一層節點資料,在此將剛剛的來源資料做走訪得到

其上一層目錄,如ABC-123-321-555 => ABC

並將此ABC, DEF存至Object[]後做回傳

getChildren部分
當點擊父節點時,就會觸發此method,此時就可以回傳該父節點實際目錄下的File resource

getParent部分
觸發時機點,當利用autoExpand時放置的element為某一子節點,如此就會觸發此method來

決定要回傳哪一個節點為此父節點

hasChildren部分
一生成treeviewer時就會執行。判斷父節點是否具備展開的資格,具備的話就會在其左邊

出現 > 的符號(若採用預設的圖示的情況下)

class ViewContentProvider implements ITreeContentProvider {
        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        }

        @Override
        public void dispose() {
        }

        @Override
        public Object[] getElements(Object inputElement) {
         File[] sources = (File[]) inputElement;
         File[] group = null;
         File[] parents = new File[sources.length];
         int i = 0;
         for(File p : sources){
          System.out.println("getElements..."+p);
          parents[i++] = p.getParentFile();
         }
         Set<File> file_set = new HashSet<File>();
         file_set.addAll(Arrays.asList(parents));
         
         group = new File[file_set.size()];
         i = 0;
         for(File p : file_set){
          group[i++] = p;
         }
            return group;
        }

        @Override
        public Object[] getChildren(Object parentElement) {
         System.out.println("getChildren..."+parentElement);
            File file = (File) parentElement;
            return file.listFiles();
        }

        @Override
        public Object getParent(Object element) {
         System.out.println("getParent..."+element);
            File file = (File) element;
            return file.getParentFile();
        }

        @Override
        public boolean hasChildren(Object element) {
         System.out.println("hasChildren..."+element);
            File file = (File) element;
            if (file.isDirectory()) {
             File[] files = file.listFiles(new FilenameFilter() {
                 public boolean accept(File dir, String name) {
                     return name.equals("commons.properties");
                 }
             });
             return (files.length == 0) ? true : false;
            }
            return false;
        }
 }

#定義treeviewer column及label顯示規則

呼叫以下method為建立column是否為pack及label的顯示規則
createColumn("NAME1", new ViewLabelProvider(true));
createColumn("NAME2", new ViewLabelProvider(false));

如在此會建立兩欄,分別為NAME1、NAME2

若為NAME2欄,在父節點部分該欄不顯示資料

private void createColumn(String name, ColumnLabelProvider labelProvider){
  TreeViewerColumn viewerColumn = new TreeViewerColumn(viewer, SWT.NONE);
  TreeColumn column = viewerColumn.getColumn();
  column.setText(name);
  //column.setResizable(true);
  viewerColumn.setLabelProvider(labelProvider);
 }

class ViewLabelProvider extends ColumnLabelProvider {
        private boolean showGroupName;
        
        public ViewLabelProvider(boolean showGroupName){
         this.showGroupName = showGroupName;
        }
        
        @Override
        public String getText(Object element) {
            if (element instanceof File) {
                File file = (File) element;
                if(file.getParentFile().getName().equals("treeviewer")){
                 if(this.showGroupName)
                  return getFileName(file);
                 else
                  return "";
                }else{
                 return getFileName(file);
                }
            }
            return null;
        }

        private String getFileName(File file) {
            String name = file.getName();
            return name.isEmpty() ? file.getPath() : name;
        }
 }

#定義treeviewer TreeListener

主要目的為,點擊父節點展開的當下,自動pack因應可顯示內容的長度,調整column的寬

度;反之,當收起來時,自動pack當下已展開的內容長度。

需要提到的是,當展開tree時,若當下要自動調整欄寬的話,需要利用到asyncExec,否則

會無法達到調整的效果,因為您並不知道何時展開完成,因此也不知何時做調整欄寬。

private void setAutoPack(Display display){
  display.asyncExec(new Runnable() {
   @Override
   public void run() {
    // TODO Auto-generated method stub
    setPack();
   }
  });
 }
 
 private void setPack(){
  TreeColumn[] columns = tree.getColumns();
  for (TreeColumn tableColumn : columns) {
   tableColumn.pack();
  }
 }

DEMO

一開始只展示父節點

當點擊各父節點後,自動調整欄寬

補充

提完了本篇的重點autoPack之後,還有一個部分為autoExpand,重點只有一段code

viewer.expandToLevel(treeMap.get("DEF-456-8888"), 1);

還記得當我們在getResource時有針對treeMap put進各子類別的name map File resource

因此,當我們想要在一開始生成treeviewer時,自動展開某一父類別的話

可以呼叫expandToLevel,請注意第一個參數要放的是父類別的某一子類別的resource

在這一篇文章建構的tree為File type,因此放此對應的resource,treeviewer底層即會自動

幫你呼叫到setContentProvider類別定義的getParent()來取得父節點進而做展開的動作,若

回傳null則會無任何動靜!

留言