程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用Eclipse的Navigator Link Helper實現導航器與編輯器的關聯

使用Eclipse的Navigator Link Helper實現導航器與編輯器的關聯

編輯:關於JAVA

實現 LinkHelper

在上個例子中,plugin.xml 中擴展了各類擴展點後,其實並不用我們寫任何 Java 代碼,就能夠在這個 ID 為 com.example.test 的視圖上完成一些 Project Explorer 已實現的操作: 例如創建項目,文件夾,文件等,這些是通過重用 navigatorContent 實現的。

另一個需要注意的地 方是,在我們實現的這個視圖中,IResource 作為與 CNF 的直接接口,同時也扮演著“項目 / 文件夾 / 文 件”這個業務場景的模型角色。(這也是為什麼在 LinkHelper 中 new StructedSelection(file) 能直接定 位到導航器中該節點的原因)

但是在實際的項目開發過程中,並不是每一種業務模型都像文件系統這 樣易於展現。例如 Java 中的 Package 概念就需要以多個文件夾合並成一個樹結點的方式展示(中間以“.” 分隔),jar 包也需要以內部解壓的方式展現其內部樹型結構。

所以通常情況下,業務模型會有單獨 的抽象表示,並作為與 CNF 的直接接口。其優點有:

所有在 Eclipse UI 上對模型的改變,會先傳遞到域模型本身,然後通過 IResource 持久化到操作系統。 對模型的改變操作和改變的傳遞都更直接。如 圖 6所示。

圖 6. Eclipse 中的資源管理

同一套資源文件,通過不同的角度觀察需要有不同的展現,操作及存儲方式。例如一個 Java 項目在 Package Explorer 以更貼近 Java 開發的方式展現與操作,而在 Resource Explorer 則是以操作系統的視角 來展現,顯示所有的文件夾及文件。這裡看待資源的角度,就是構建業務模型所要考慮的事情。

接下來我們會在上一個例子的基礎上,加入自己的業務模型層。雖然在文件和文件夾這個場景下,這一層 的存在意義不大,但是能供讀者在解決具體的工作時借鑒。

我們仿照上個例子建立一個 custom.linkwitheditor.sample 的插件項目,但這次將自己實現大部分的功能,並加入特定的業務模型層。

首先將 view id 更改為 custom.linkwitheditor.view.myCustomNavigatorView,並添加圖標。

刪除 org.eclipse.ui.navigator.viewer 擴展下的 viewerActionBindin —— 這意味著在這個例子我們 並不擴展任何 action,context menus 等。

刪除 org.eclipse.ui.navigator.viewer 擴展下的 viewerContentBinding —— 一會我們會創建自己實 現的 navigatorContent 與 linkHelper。

接下來我們在 Java 代碼中加入自己的模型層,如 圖 7所示。

圖 7. 虛擬的模型層類圖

其 中 VirtualNode 作為與 CNF 的直接接口,ProjectNode,FolderNode,FileNode 均實現此接口。 VirtualNode 的實現代碼如 清單 2所示。(ProjectNode,FolderNode,FileNode 僅需要實現各自的 getImage() 方法,詳見文章附件的源碼)

清單 2. VirtualNode.java

public abstract 

class VirtualNode { 
   // 內部維持對此虛擬結點對 IResource(Eclipse Core Resource) 的引用
   protected IResource resource; 
   // 此虛擬結點的父結點
   protected VirtualNode parent; 
       
   public static final int PROJECT = 1; 
   public static final int FOLDER = 2; 
   public static final int FILE = 3; 
       
   public void setResource(IResource resource) { 
       this.resource = resource; 
   } 
   public IResource getResource() { 
       return resource; 
   } 
   // 虛擬結點對應的修飾圖像,各實現類均有不同實現
   public Image getImage() { 
       return null; 
   } 
       
   // 虛擬結點顯示的文本,各實現類均有不同實現
   public String getText() { 
       return resource.getName(); 
   } 
   public VirtualNode getParent() { 
       return parent; 
   } 
   public void setParent(VirtualNode parent) { 
       this.parent = parent; 
   } 
}

接下來我們將實現如何將業務模型層傳遞給 CNF 框架展示,其中 MyCustomLabelProvider 比較簡 單,只需要調用相應 VirtualNode 的 getImage() 和 getText(),運行時利用 Java 的多態就會調用各子類 的實現。

清單 3. MyCustomLabelProvider.java

public class MyCustomLabelProvider 

extends LabelProvider { 
    
public Image getImage(Object element) { 
if (element instanceof VirtualNode) 
return ((VirtualNode) element).getImage(); 
return super.getImage(element); 
} 
    
public String getText(Object element) { 
if (element instanceof VirtualNode) { 
return ((VirtualNode) element).getText(); 
} 
return super.getText(element); 
} 
}

MyCustomContentProvider 的設計會復雜一些:

內部維護一個 IResource 和 VirtualNode 之間關聯關系的 hash map,當有新結點生成時注冊至此 hash map。( 另一種方式是實現 Eclipse 內部的 ICommonViewerMapper,並將此 hash map 關聯到視圖上。這樣的好處可以獲得 Eclipse 內部其他事件的通知 )

清單 4. MyCustomContentProvider.java (part 1)

// 內部維護一個 IResource 和 

IVirtualNode 相關聯的 hash map 
// 主要是為了之後擴展 Link With Editor 功能使用
private Map<IResource, VirtualNode> resources2NodesMap; 
    
public Map<IResource, VirtualNode> getResources2NodesMap() { 
return resources2NodesMap; 
} 
    
/** 
* 以 resource 為 key,virtualNode 為 value,注冊到 resources2NodesMap 中
* @param resource 
* @param virtualNode 
*/
private void register(IResource resource, VirtualNode virtualNode) { 
if (register) 
INSTANCE.resources2NodesMap.put(resource, virtualNode); 
   }

獲得子結點以及創建新結點實現

清單 5. MyCustomContentProvider.java (part 2)

/** 
* 返回當前結點的子結點,並將子結點納入 resources2NodesMap 的管理
*/
public Object[] getChildren(Object element) { 
List<VirtualNode> projectNodes = new ArrayList<VirtualNode>(); 
// 如果當前傳入參數為 Eclipse 的根工作空間
if (element instanceof IWorkspaceRoot) { 
// 遍歷根工作空間下的所有 IProject 
for (Object o : super.getChildren(element)) { 
// 根據 IProject 創建相應的 Project 虛擬結點
VirtualNode virtualNode = makeNode(o, null, VirtualNode.PROJECT); 
projectNodes.add(virtualNode); 
// 向 resources2NodesMap 注冊當前虛擬結點
register((IResource) o, virtualNode); 
} 
} 
// 如果傳入的是虛擬結點
else if (element instanceof VirtualNode) { 
VirtualNode node = (VirtualNode) element; 
for (Object o : super.getChildren(node.getResource())) { 
// 過濾以 . 開頭的文件和文件夾
if (((IResource) o).getName().startsWith(".")) 
continue; 
    
// 創建並注冊 Folder 虛擬結點
if (o instanceof IFolder) { 
VirtualNode virtualNode = makeNode(o, node, VirtualNode.FOLDER); 
projectNodes.add(virtualNode); 
register((IResource) o, virtualNode); 
} 
// 創建並注冊 File 虛擬結點
else if (o instanceof IFile) { 
VirtualNode virtualNode = makeNode(o, node, VirtualNode.FILE); 
projectNodes.add(virtualNode); 
register((IResource) o, virtualNode); 
} 
} 
} 
    
// 返回當前傳入元素的子結點
return projectNodes.toArray(); 
} 
    
/** 
* 創建對應於域模型的虛擬結點
* @param o IResource 對象
* @param parent 父虛擬結點
* @param nodeType 結點類型
* @return 新創建的或已注冊的虛擬結點
*/
private VirtualNode makeNode(Object o, VirtualNode parent, int nodeType) { 
VirtualNode virtualNode = null; 
if (resources2NodesMap.get(o) == null) { 
switch (nodeType) { 
case VirtualNode.PROJECT: 
virtualNode = new ProjectNode(); 
break; 
case VirtualNode.FOLDER: 
virtualNode = new FolderNode(); 
break; 
case VirtualNode.FILE: 
virtualNode = new FileNode(); 
break; 
default: 
break; 
} 
} 
else { 
virtualNode = (VirtualNode) resources2NodesMap.get(o); 
} 
virtualNode.setResource((IResource) o); 
virtualNode.setParent(parent); 
return virtualNode; 
   }

由於 getChildren() 邏輯會將結點注冊,但 hasChildren() 的實現會調用 getChildren()。 所以為了在判斷 N 級結點有沒有子結點,不將子結點注冊,所以用一個局部變量來避免這種情況的發生。

清單 6. MyCustomContentProvider.java (part 3)

// 檢查當前觸發條件是否應將虛擬結點

納入 resources2NodesMap 管理
private boolean register = true; 
    
public boolean hasChildren(Object element) { 
// 加入 register 變量是為了保證在應顯示 N 級結點時,不會由於 hasChildren 的觸發
// 而導致將 N+1 級結點也納入 resources2NodesMap 
register = false; 
boolean flag = super.hasChildren(element); 
register = true; 
return flag; 
   }

接下來就可以實現針對於業務模層的 linkHelper 實現:其中最關鍵的代碼在於如何處理 resources2NodesMap 為空時如何關聯到導航器結點(例如 workspace 第一次被打開時)。另外業務模型層中 的 parent 也很重要,這關系到在 JFace 內部是否能從葉結點一步步回溯(有興趣的讀者可以試試把 VirtualNode#parent 刪除,做一個對比)。而一些重復代碼的注釋參見 清單 1。

清單 7. MyLinkHelper.java

public class MyLinkHelper implements ILinkHelper { 
    
public IStructuredSelection findSelection(IEditorInput anInput) { 
IFile file = ResourceUtil.getFile(anInput); 
VirtualNode virtualNode = null; 
if (file != null) { 
virtualNode = (VirtualNode) MyCustomContentProvider.INSTANCE 
.getResources2NodesMap().get(file); 
// 如果此時 map 未初始化
if (virtualNode == null) { 
IProject project = file.getProject(); 
// 1) 模擬展開 project 結點動作
MyCustomContentProvider.INSTANCE.getChildren(MyCustomContentProvider 
.INSTANCE.getResources2NodesMap().get(project)); 
    
// 2) 逐層向上查找 project root 
IResource parent = file.getParent(); 
Stack<IResource> stack = new Stack<IResource>(); 
    
// 2.1) 將查找過程中的 IResource 對象逐個壓棧
while (parent != project) { 
stack.push(parent); 
parent = parent.getParent(); 
} 
    
// 2.2) 逐個 IResource 對象出棧,並模擬展開相應樹結點動作
while (!stack.isEmpty()) { 
parent = stack.pop(); 
MyCustomContentProvider.INSTANCE.getChildren(MyCustomContentProvider 
.INSTANCE.getResources2NodesMap().get(parent)); 
} 
    
// 3) 得到最終的底層結點
virtualNode = (VirtualNode) MyCustomContentProvider.INSTANCE 
.getResources2NodesMap().get(file); 
} 
} 
return virtualNode != null ? new StructuredSelection(virtualNode) : 
StructuredSelection.EMPTY; 
} 
    
public void activateEditor(IWorkbenchPage aPage, 
IStructuredSelection aSelection) { 
if (aSelection == null || aSelection.isEmpty()) 
return; 
if (aSelection.getFirstElement() instanceof FileNode) { 
IFile file = (IFile) ((FileNode) aSelection.getFirstElement()).getResource(); 
IEditorInput fileInput = new FileEditorInput(file); 
IEditorPart editor = null; 
if ((editor = aPage.findEditor(fileInput)) != null) 
aPage.bringToTop(editor); 
    
} 
} 
}

最後我們將已實現的類定義在 plugin.xml。

圖 8. plugin.xml

我們來看一看運 行的效果:

由於我們並沒有在 myCustomNavigatorView 中擴展任何的 action, menu, wizard 等,所以先在 Project Explorer 中創建一些文件。

圖 9. 准備示例所用的文件

切換到 Customized Navigator,點擊 Link With Editor:test1.txt 相對應的導航器結點被選中。

圖 10. 編輯器與導航器結點的關聯

在導航器視圖上切換 test1.txt 結點與 test2.txt 結點,編輯器的當前編輯文件也隨之切換。

總結

Link With Editor 功能運用的范圍非常廣泛,常見的場景除了定位編輯器中打開的各文件, 還有:

利用快捷鍵 (Ctrl+Shift+R/T) 打開文件,定位其在導航器視圖中的位置(例如查看 Eclipse 源碼時)

拖拽導航器視圖中的某結點進入編輯器,這樣必須首先定位該結點在導航器視圖中的位置(例如本文的 圖 7就是這樣自動生成的)

所以在開發 Eclipse 插件應用時,實現此功能是非常必要的。而通過本文第二個例子也可以看出,Link With Editor 功能的實現需要首先理解 CNF 的框架設計思想與實現規范,進而設計業務模型與資源文件間的 映射關系。

樣例代碼

SrcCodePackage.zip

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved