Eclipse提供了非常多的view,從表現(xiàn)形式來說可分為table view和tree view;從結(jié)構(gòu)上來說可分成三類:Common navigator view, Pagebook view, Task-oriented view。一般情況下,CNV與Resource有關,pagebook跟selection有關,而task-oriented 為自定義的視圖?;舅械?* explorer都是CNV型的view,基本所有主要插件都有CNV的影子比如IDE,Navigator, Team,JDT, CDT, DTP等。為什么要使用CNV? Paper Napkin的文章說的很清楚了,我的看法是,對于怎樣面對大批量+復雜應用的二次抽象(view超多,view內(nèi)容聯(lián)系超復雜緊密),CNV提供了一個很好的完整實例。
==>> 代碼下載,下載后import到Eclipse 3.4.1+JDK 1.6,run/debug即可。
CNV的視圖特征:
 10分鐘,一個CNV Resource View
- 新建一個plugin項目,名字 com.lifesting.hush,將圖標解壓縮到項目下,刷新,打開MANIFEST.MF,在build項里面將icon目錄鉤上。
- 定位Dependencies項,依次加入 org.eclipse.ui.navigator,org.eclipse.ui.navigator.resources,org.eclipse.ui.ide,org.eclipse.jface.text,org.eclipse.ui.editors, org.eclipse.core.resources,org.eclipse.ui.views插件。
- 配置一個view extension, 如下圖:

需要注意的是,這個view的implementation是navigator插件里面的CommonNavigator,目前我們不需要手寫任何代碼。
- 使用Extension Point org.eclipse.ui.navigator.viewer, new一個viewer,viewId設置為 com.lifesting.hush.view.cnf,popMenuId暫時置空;new一個viewerContentBinding,viewId不變,添加一個includes子節(jié)點,然后在其上添加一個contentExtension,屬性pattern為org.eclipse.ui.navigator.resourceContent,isRoot為true.
- 啟動,點擊菜單Window->Show View->General->Html Explorer,就可以看到效果了,如果view是空白,也不是bug,在左邊的Pakcage Explorer或Resource Explorer新建一個項目,然后關閉Html Explorer再打開,就會看到Html Explorer顯示的和Resource Explorer一模一樣的項目結(jié)構(gòu)。
雖然這個Html Explorer出來了,但設置的org.eclipse.ui.navigator.resourceContent哪來的?怎么定義的?怎么添加右鍵菜單?Link為啥無效?怎樣定制這個顯示?CNF好像也沒有顯著的特點阿?不著急,逐一搞定,從頭開始,最終的效果會是這樣的:

CNV的核心是navigatorContent,所有操作都是圍繞它展開的(可以選擇org.eclipse.ui.navigator.navigatorContent,選擇find references,看看SDK都提供了哪些content),我們這個Html Explorer為了把過程將的更清楚,將使用兩個自定義的navigatorContent。下面是步驟:
- 通過extension point org.eclipse.ui.navigator.navigatorContent 新建一個id為com.lifesting.cnf.directorycontent的navigatorContent,activatorByDefault=true,LabelProvider=
org.eclipse.ui.model.WorkbenchLabelProvider,而contentProvider需要新建一個類,非常簡單,就是遍歷IProject或IFolder的子資源(Folder或File)。它的getElement方法實現(xiàn):
@Override public Object[] getElements(Object inputElement) { if (inputElement instanceof IProject) { try { return ((IProject)inputElement).members(); } catch (CoreException e) { e.printStackTrace(); } } else if (inputElement instanceof IFolder){ try { return ((IFolder)inputElement).members(); } catch (CoreException e) { e.printStackTrace(); } } return EMPTY; }
- 每個navigatorContent都有triggerPoints,很顯然剛才定義的content通過IProject和IFolder來觸發(fā)view tree生成。在這個content下面new 一個triggerPoints,再new兩個instanceof分別指向IProject和IFile。
- 在定義actionProvider的時候,需要知道selection大致的類型,在這個content下面new一個possibleChildren,再new一個instanceof 先后IResource(IFile或者IFolder)。
- 通過extension point org.eclipse.ui.viewActions給ui view添加一個action用來設置content的Root,它的class如下:
//bind to mycnfview public class OpenDirectoryAction implements IViewActionDelegate { private MyCnfView view; public OpenDirectoryAction() { }
@Override public void init(IViewPart view) { this.view = (MyCnfView) view; } @Override public void run(IAction action) { DirectoryDialog dir_dialog = new DirectoryDialog(view.getSite() .getShell()); String dir_location = retriveSavedDirLocation(); initDialog(dir_dialog, dir_location); String dir = dir_dialog.open(); if (null != dir && !dir.equals(dir_location)) { saveDirLocation(dir); createPhantomProject(dir); fireDirChanged(dir); } }
private void createPhantomProject(String dir_location) { IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(MyCnfView.PHANTOM_PROJECT_NAME); // 1 delete previous defined project if (project.exists()) { try { project.delete(false, true, null); } catch (CoreException e) { e.printStackTrace(); } } // 2 create new project with the same name final IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(MyCnfView.PHANTOM_PROJECT_NAME); desc.setLocationURI(new File(dir_location).toURI()); IRunnableWithProgress op = new IRunnableWithProgress() { public void run(IProgressMonitor monitor) throws InvocationTargetException { CreateProjectOperation op = new CreateProjectOperation(desc, "Build Algorithm Library"); try { PlatformUI.getWorkbench().getOperationSupport() .getOperationHistory().execute( op, monitor, WorkspaceUndoUtil .getUIInfoAdapter(view.getSite().getShell())); } catch (ExecutionException e) { throw new InvocationTargetException(e); } } }; try { view.getSite().getWorkbenchWindow().run(false, false, op); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } // 3 add the new created project to default workingset if (project.exists()) { view.getSite().getWorkbenchWindow().getWorkbench().getWorkingSetManager().addToWorkingSets(project, new IWorkingSet[] {}); //4 waiting the project is ready(file structure is built) try { project.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e) { e.printStackTrace(); } } } //...略..輔助方法 }
代碼要表達的就是建立一個隱含的project,將action取得的directory下所有的文件都倒入到項目中來。
- 將ui view的class從CommonNavigator變?yōu)橐粋€它的子類MyCnfView:
public class MyCnfView extends CommonNavigator {
public static final String KEY_DIR_LOCATION="com.lifesting.cnf.myview_location"; public static final String PHANTOM_PROJECT_NAME=".htmlproject"; public MyCnfView() { } public IAdaptable getProjectInput(){ IWorkspaceRoot ws_root = ResourcesPlugin.getWorkspace().getRoot(); IProject proj = ws_root.getProject(PHANTOM_PROJECT_NAME); if (!proj.exists()) return getSite().getPage().getInput(); return proj; } public void reset() { getCommonViewer().setInput(getProjectInput()); getCommonViewer().refresh(); } @Override protected IAdaptable getInitialInput() { return getProjectInput(); }
}
- 將viewerContentBinding/includes的contentExtension的pattern替換為剛才定義的com.lifesting.cnf.directorycontent。
- 因為是Html Explorer,需要過濾掉非html文件,需要設置一個過濾器。通過extension point org.eclipse.ui.navigator.navigatorContent 新建一個id為com.lifesting.cnf.filter.nothtml的filter,它的class非常簡單:
public class NotHtmlFilter extends ViewerFilter {
public NotHtmlFilter() { }
@Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof IFile) { return Util.isHtmlFile((IFile)element); } return true; } }
再將此filter配置到cnv的viewerContentBinding/includes中去,跟contentExtension配置過程一樣。
- 啟動后,cnv已經(jīng)可以工作,為了演示navigatorContent的可重復利用性,再定義一個只包含html文檔標題的html title content(為方便只掃描標題),掛在前面定義的directory content上。directory content的model是IProject/IFile/IFolder,html title content需要定義一個model,一個html文檔掃描器,還有contentPrvoider和lableProvider。
- model
public class HeadTitle { private String title; private IFile file; private int from = 0; public int getFrom() { return from; } //略set/get }
- scaner
public static HeadTitle parse(InputStream in) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(in)); int c = -1; StringBuffer sb = new StringBuffer(); boolean tag = false; boolean found_title = false; String to_match = "title"; HeadTitle title = new HeadTitle(); int counter = 0; int start = 0; outer: while ((c = br.read()) != -1) { if (c == '<') { br.mark(3); if (br.read() == '!' && br.read() == '-' && br.read() == '-') { // loop over html comment until --> counter += 3; int t1, t2, t3; t1 = t2 = t3 = 0; while ((c = br.read()) != -1) { t3 = t2; t2 = t1; t1 = c; counter++; if (t3 == '-' && t2 == '-' && t1 == '>') { counter++; // '<' also need be countered continue outer; } } break outer; //reach the end } else { br.reset(); } tag = true; if (found_title) { title.setTitle(sb.toString()); title.setFrom(start); title.setTo(counter); return title; } } else if (c == '>') { start = counter + 1; if (tag) { String s = sb.toString().trim(); found_title = to_match.equalsIgnoreCase(s); sb.setLength(0); tag = false; } } else { sb.append((char) c); } counter++; } title.setTitle("No title"); return title; }
- contentProvider只有一個getChildren比較重要
private static final Object[] EMPTY = new Object[0]; @Override public Object[] getChildren(Object parentElement) { if (parentElement instanceof IFile) { IFile f = (IFile) parentElement; if(Util.isHtmlFile(f)) { try { HeadTitle head = SimpleHtmlParser.parse(f.getContents()); head.setFile(f); return new HeadTitle[]{head}; } catch (IOException e) { e.printStackTrace(); } catch (CoreException e) { e.printStackTrace(); } } } return EMPTY; }
- labelProivder
public class HtmlTitleLabelProvider extends LabelProvider { public static final String KEY_TITLE_IMAGE="icon/title.GIF"; @Override public String getText(Object element) { if (element instanceof HeadTitle) return ((HeadTitle)element).getTitle(); else if (element instanceof IFile) return ((IFile)element).getName(); return super.getText(element); } @Override public Image getImage(Object element) { if (element instanceof HeadTitle) { Image img = Activator.getDefault().getImageRegistry().get(KEY_TITLE_IMAGE); if (img == null) { Activator.getDefault().getImageRegistry().put(KEY_TITLE_IMAGE, (img = Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, KEY_TITLE_IMAGE).createImage())); } return img; } return super.getImage(element); } }
- html title content利用directory content找到文件,提取標題,但二者有個東西來觸發(fā)這個過程。在html title content下定義個一個triggerPoints,使用instanceof=IFile來觸發(fā)。
- 所有的功能基本完成,剩下popmenu和link,popmenu可以有兩種方式, contribute或cnv下的popmenu子節(jié)點.contribute會在popmenu下建一堆比如group.*的menu placeholder。content下可以配置actionProvider來完成popmenu的功能,為簡單只在popmenu上放置一個open的動作,即open html file,如果是html file,直接打開;如果是html file title,還須將html title高亮顯示,以示不通,actionProivder:
public class MyCommonActionProvider extends CommonActionProvider {
private IAction action; public MyCommonActionProvider() { } @Override public void init(ICommonActionExtensionSite site) { super.init(site); ICommonViewerSite check_site = site.getViewSite(); if (check_site instanceof ICommonViewerWorkbenchSite) { ICommonViewerWorkbenchSite commonViewerWorkbenchSite = (ICommonViewerWorkbenchSite)check_site; action = new OpenFileAction(commonViewerWorkbenchSite.getPage(),commonViewerWorkbenchSite.getSelectionProvider()); } } @Override public void fillActionBars(IActionBars actionBars) { super.fillActionBars(actionBars); actionBars.setGlobalActionHandler(ICommonActionConstants.OPEN, action); } @Override public void fillContextMenu(IMenuManager menu) { super.fillContextMenu(menu); if (action.isEnabled()) menu.appendToGroup("group.edit", action); } }
open file action:
public class OpenFileAction extends Action {
private IWorkbenchPage page; private ISelectionProvider provider; private Object selected = null; public OpenFileAction(IWorkbenchPage page, ISelectionProvider selectionProvider) { this.page = page; this.provider = selectionProvider; setText("Open"); setDescription("Doo"); setImageDescriptor(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icon/lookin.GIF")); } @Override public boolean isEnabled() { ISelection selection = provider.getSelection(); if(!selection.isEmpty()) { IStructuredSelection structuredSelection = (IStructuredSelection)selection; Object element = structuredSelection.getFirstElement(); selected = element; return element instanceof IFile || element instanceof HeadTitle; } selected = null; return false; } @Override public void run() { if (null == selected) return ; IFile file = ((selected instanceof HeadTitle) ? ((HeadTitle)selected).getFile() : (IFile)selected); FileEditorInput fileEditInput = new FileEditorInput(file); try { TextEditor editor = (TextEditor) page.openEditor(fileEditInput, "org.eclipse.ui.DefaultTextEditor"); if (selected instanceof HeadTitle) { int from = ((HeadTitle)selected).getFrom(); int to = ((HeadTitle)selected).getTo(); editor.selectAndReveal(from, to-from); } } catch (PartInitException e) { e.printStackTrace(); } } }
- Link功能非常簡單,使用extension point org.eclipse.ui.navigator.linkHelper,它有兩個子節(jié)點selectionEnablement和editorinputEnablement,分別對應在view中的selection和打開editor中的editorInput,class為:
public class SimpleHtmlLinkHelper implements ILinkHelper {
@Override public void activateEditor(IWorkbenchPage page, IStructuredSelection selection) { Object obj = selection.getFirstElement(); if (obj instanceof IFile) { FileEditorInput input = new FileEditorInput((IFile) obj); IEditorPart editor = page.findEditor(input); if(editor != null) { page.bringToTop(editor); } } }
@Override public IStructuredSelection findSelection(IEditorInput anInput) { if (anInput instanceof IFileEditorInput) { IFile file = ((IFileEditorInput)anInput).getFile(); StructuredSelection selection = new StructuredSelection(file); return selection; } return null; }
}
插件太復雜,不適合一篇blog講清楚,如果有人對cnv有些比明白,歡迎來郵件討論。
|