引言
Eclipse Platform 為工具開發提供一組健壯的服務和 API。它使來自完全不同的供應商的工具之間的集成變得平滑,為不同類型的開發工作創建了一個無縫的環境。
Eclipse Platform 的軟件組件之一就是 SWT。盡管 SWT 不是 Platform 的一個核心組件集,但它還是不可或缺的,因為它為產品和插件開發者提供了一組基於 Java 的 GUI 小窗口。SWT 與操作系統無關且非常方便,然而它的底層 JNI 接口將展現本機平台的外觀和感覺(look-and-feel)以及性能。
總體上講,對於那些想要編寫在 Platform 的各種框架中運行良好且視覺上有吸引力的插件的開發者和供應商來說,SWT 提供了一個優秀的解決方案。然而,SWT 與 Java 的 Swing GUI 小窗口之間的互操作性程度相當低,這一點對 SWT 影響很大。例如,Swing 和 SWT 使用完全不同的事件處理機制。這個差異常常會使由 Swing 和 SWT 共同組成的 GUI 不可用。
為了在 Swing 和 SWT 之間提供一個接口以便提供可接受級別的兼容性,我們已經做了一些工作,比如使開發者能夠將 Swing 小窗口嵌入到 SWT 中的 org.eclipse.swt.internal.swt.win32.SWT_AWT 實用程序類。但是,這些方法仍然是實驗性的,尚未獲得官方支持 — 由此包名內含有“internal”。
這個拙劣的互操作性對於 Eclipse 項目和工具供應商來說,都是令人遺憾的障礙。目前,大量軟件開發和測試工具提供用 Swing 編寫的用戶界面。將一個帶有復雜的 Swing GUI 的現有工具移植到 SWT 需要來自供應商的相當多的時間和投資。盡管 Eclipse Platform 具有了所有先天的優勢,但是 Swing 和 SWT 之間拙劣的互操作性導致開發成果不那麼吸引人。
本文向您說明了如何實現下列操作:
啟動一個基於 Swing 的編輯器以編輯 Eclipse Platform Workbench 中任何名為“ThirdParty.java”的 Java 文件
將 Swing 編輯器中所作的源代碼更改帶回到 Workbench 中
使用 Preference Page 框架控制 Swing 編輯器的屬性
使 Swing 編輯器成為“Workbench 知曉的”
從 Swing 編輯器中啟動一個 SWT 小窗口
本文引入了一些簡單的技術來實現上述操作,無需使用任何不被支持的 API。我們不引用任何內部類並且遵守所有通用的插件規則。為了最有效地使用這些技術,您應該具有編寫插件和使用插件開發環境(Plug-in Development Environment)的基本知識,您還應該具有對基於 Swing 的編輯器的源代碼的訪問權。
假定的 Swing 編輯器:Ed
為了模擬真實的各種工具集成的情況,我們來使用一個假定的基於 Swing 的編輯器(名為“Ed”)。下面是 Ed 的一些特征:
Ed 是基於 Swing 的編輯器。
Ed 繼承了 JFrame。
Ed 只處理具有特定名稱 ThirdParty.java 的 Java 文件上。
Ed 用一個 JEditorPane和一個 JButton作為私有域。JEditorPane 顯示 ThirdParty.java 的所有源代碼。 JButton保存源代碼。
Ed 是帶有一個 main() 方法的獨立的 Java 應用程序。
Ed 被設計為在命令提示符下運行。它並不知曉 Eclipse Platform Workbench。
基本概念
由於 Swing 和 SWT 互操作性的限制,難以獲得這二者之間的直接通信(如事件處理)。在既不使用不被支持的 API 也不服從任何插件開發規則的情況下,實現這個目的的一條途徑就是避免它們彼此嵌入,取而代之的是讓它們擁有獨立的 Frame。插件類(或者說 Singleton 實用程序類)將處理它們之間的通信,如 圖 1所示。例如,Ed 上的 JButton 可以使一個 SWT Shell 出現,顯示已編輯的 ThirdParty.java 的一些特定於 Workbench 的屬性(如 Project References)。
圖 1. Singleton 實用程序類
編輯器集成
集成的主要目的是開發一個用 Ed 作為在 Workbench 中找到的任何 ThirdParty.java 的缺省編輯器的插件。
准備插件項目
在 Workbench 中,創建一個新的插件項目“org.eclipse.jumpstart.editorintegration”,然後選擇 Create plug-in project 向導中的“Create plug-in using a template wizard”選項。單擊 Next。選中“Add default instance acces”選項,然後單擊 Finish。Workbench 切換到 Plug-in Development Perspective。一個空白的插件清單(manifest)文件以及繼承了 AbstractUIPlugin 的插件類 EditorintegrationPlugin 是被自動創建的。還生成了插件類的一個私有靜態實例以及 getter 方法。
插件清單文件編輯器應該是打開的;如果沒打開,雙擊 plugin.xml 啟動它。
該插件需要下面這些庫。將它們添加到插件項目的 Java Build Path 下:
ECLIPSE_HOME/plugins/org.eclipse.core.resources/resources.jar
ECLIPSE_HOME/plugins/org.eclipse.jdt.core/jdtcore.jar
ECLIPSE_HOME/plugins/org.eclipse.jdt.ui/jdt.jar
ECLIPSE_HOME/plugins/org.eclipse.swt/swt.jar
ECLIPSE_HOME/plugins/org.eclipse.ui/workbench.jar
插件清單文件
因為這個插件只處理名為 ThirdParty.java 的 Java 文件,所以我們需要為這些 Java 文件指定一個編輯器。在插件清單文件編輯器中,切換到 Extensions 選項卡,然後添加擴展點“Internal and External Editors”。將 default 設為“true”,將 name 設為“Ed - Swing Editor”,將 filenames 設為“ThirdParty.java”,將 launcher 設為“org.eclipse.jumpstart.editorintegration.EdLauncher”。添加的擴展點的源代碼看上去應該如清單 1 所示:
清單 1. 添加一個擴展點
<extension point="org.eclipse.ui.editors">
<editor
name="Ed - Swing Editor"
default="true"
icon="icons/thirdparty.gif"
filenames="ThirdParty.java"
launcher="org.eclipse.jumpstart.editorintegration.EdLauncher"
id="org.eclipse.jumpstart.editorintegration.ed">
</editor>
</extension>
Ed 現在是所有 ThirdParty.java 文件的缺省編輯器,如 圖 2所示。
圖 2. Ed 是所有 ThirdParty.java 文件的缺省編輯器
請注意:一定要包括 icons/thirdparty.gif 文件,它被作為“Open With”菜單中所有 ThirdParty.java 文件的缺省編輯器顯示。
集成 Ed 源代碼
將 Ed 的源代碼導入到插件項目中。如何調用 Ed 由您決定。插件類可以包含一個 Ed 私有域以及一個相應的公有 getter 方法:
清單 2. Ed 作為私有域
private Ed ed = null;
public Ed getEd()
{
if (ed == null)
{
ed = new Ed ();
}
return ed;
}
另外,插件類可以為每個已啟動的 ThirdParty.java 文件返回 Ed 的一個單獨的實例。您在該插件維護和提供的 Singleton 實用程序類中實現這兩種方式中的哪一種都可以。
編輯器啟動程序(launcher)
因為插件使用擴展點 org.eclipse.ui.editors ,所以它必須為 Eclipse Platform 提供一個清單文件中指定的編輯器啟動程序類。
創建類 org.eclipse.jumpstart.editorintegration.EdLauncher 以實現接口 IEditorLauncher(如果沒找到這個接口,請確保 workbench.jar 文件包含在 Project Path 中;請參閱 准備插件項目)。請一定要選中 Wizard 中的“Inherited abstract methods”選項。
每次雙擊 ThirdParty.java 文件時,Eclipse Platform 都執行 EdLauncher.open(IFile) 來調用該文件類型的缺省編輯器。Platform 將單擊的構件作為 IFile 傳送給方法。在這種情況下,IFile 是一個 Java 源文件,因此您可以將它因此您可以將它強制轉型為 ICompilationUnit。
由於 Ed 並不是為了處理 JDT 對象而設計的,所以您必須從 ICompilationUnit 中抽取源代碼內容並將它放到 Ed 中以便查看:
EditorintegrationPlugin.getDefault().getEd().getEditorPane().setText
(ICompilationUnit.getSource());
EditorintegrationPlugin.getDefault().getEd().show();
一旦執行了 show() 方法,Ed 就被作為主 Workbench 窗口外部的一個 JFrame 顯示(請參見 圖 3)。插件記錄已編輯的 ThirdParty.java 的項目名稱和包名稱。當您試圖保存 Ed 中所作的更改時,該信息是至關重要的。
圖 3. Swing 編輯器顯示在 Workbench 外面
雙向傳遞(round-tripping):將源代碼的更改返回到 Workbench 中
傳統的編輯器將在平面文件、二進制資源庫中保存源代碼,或者將源代碼保存到源代碼控制系統中。作為一個編輯器,Ed 需要一些方法來保存它顯示的對源代碼的更改。
Ed 有一個“Save”按鈕( JButton),如 Swing 編輯器:Ed 中所描述。按下按鈕後, actionPerformed() 方法被調用,Save 按鈕觸發一個事件。實現一個事件偵聽器的對象接收事件並執行源代碼保存操作。
您可以用 Singleton 實用程序類(請參閱 編輯器啟動程序)作為實現事件偵聽器的對象。實用程序類一接收到來自 Save 按鈕的事件對象,就從 Ed 中抽取源代碼,然後將源代碼放入對應的 Workbench 對象中。保存到文件系統的實際工作被委托給 Eclipse Platform。
多個文件在 Workbench 中可能擁有相同的名稱。這就是 ThirdParty.java 的項目名稱和包名稱有用的地方。該信息由插件存儲。確切的實現方式由您決定。假定編輯器存儲信息,您可以在實用程序類中使用下列代碼片段(snippet):
清單 3. 管理文件名稱
public void saveButtonPressed() {
try {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject myProj = root.getProject(getEd().getProjectname());
IFolder myFolder = myProj.getFolder(getEd().getPackageName());
IJavaElement myPackageFragment = JavaCore.create(myFolder);
if (myPackageFragment != null) {
IPackageFragment packageFrag = (IPackageFragment)myPackageFragment;
String sourceFromEd = getEd().getJEditorPane1().getText();
ICompilationUnit icu = packageFrag.getCompilationUnit("ThirdParty.java");
icu.getBuffer().setContents(sourceFromValidator);
icu.save(null, true);
}
else {
System.out.println("myPackageFragment is null.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
逆向進行雙向傳遞
清單 3 處理“正向”雙向傳遞。還需要“反向”雙向傳遞來把用 Eclipse Platform 的 JDT Java 編輯器在 ThirdParty.java 中所作的任何更改帶回到 Ed。
實用程序類可以實現接口 org.eclipse.jdt.core.IElementChangedListener ,您可以用這個接口跟蹤對任何 IElements(包括 ICompilationUnit)作的更改。當源代碼更改被引入到 Workbench 中的 Java 文件內時,調用方法 elementChanged(ElementChangedEvent) 。
您需要有選擇地過濾出那些不涉及 Ed 插件的 IElement 更改。一種過濾方式是從 IElementChangedEvent 參數中抽取 IJavaElementDelta 對象並對其進行檢查。例如,下面的語句過濾 Ed 插件情況下不相關的源代碼更改。
清單 4. 過濾不相關的源代碼更改
IJavaElementDelta delta = event.getDelta();
if (delta != null) {
if(delta.getElement().getElementName().equalsIgnoreCase("ThirdParty.java")) {
//code to update Ed's editor panel.
}
}
對於非 Java 構件的編輯器,IElementChangedListener 不能捕獲在 Workbench 中所作的更改。Eclipse Platform 提供接口 org.eclipse.core.resources.IResourceChangeListener 來處理對非 Java 資源所作的更改。
首選項頁面
要為用戶提供豐富的、易於使用的功能,工具應該提供可以通過啟動參數訪問的、或者可以通過 GUI(它不是編輯器的核心圖形界面的一部分)訪問的可配置的選項。在用於 Eclipse Platform 的插件的情況中,強烈推薦通過 Platform 的 Preference Page 框架(Window -> Preferences)對這些選項進行配置。
為了舉例起見,我們將 Ed 的顏色作為一個使用 Platform 首選項頁面的可配置的選項來控制。
在插件清單文件中添加一個首選項頁面擴展點
在 Eclipse Platform 中,首選項頁面被定義為一個擴展點。要使用它,請將它添加到插件清單文件編輯器中,或者將下列代碼放入 plugin.xml 中:
清單 5. 將首選項頁面添加到 plugin.xml
<extension
id="org.eclipse.jumpstart.editorintegration.pref"
name="Ed Preference"
point="org.eclipse.ui.preferencePages">
<page
name="Swing Editor Preference Page"
class="org.eclipse.jumpstart.editorintegration.EdPreferencePage1"
id="Swing Editor Preference Page"
</page>
</extension>
首選項頁面類
首選項頁面繼承了 org.eclipse.jface.preference.PreferencePage 。在這個示例中,簡單的首選項頁面由三個最大值為 255 的滑動條(slider bar)組成,表示 Ed 的 java.awt.Color 對象的顏色(紅、綠和藍)。
在插件項目中創建清單文件中指定的類 org.eclipse.jumpstart.editorintegration.EdPreferencePage1 。這個類必須繼承 org.eclipse.jface.preference.PreferencePage 並實現接口 org.eclipse.ui.IWorkbenchPreferencePage 。
首選項頁面呈現出與編輯器啟動程序類似的編碼問題:JFace/SWT 將如何與 Swing 通信?幸運的是,同樣的方式適用。例如, performApply() 方法可能看上去像這樣:
清單 6. performApply() 方法
protected void performApply() {
int red = redSWTSlider.getSelection();
int green = greenSWTSlider.getSelection();
int blue = blueSWTSlider.getSelection();
java.awt.Color newColor = new java.awt.Color(red, green, blue);
EditorintegrationPlugin.getDefault().getEd().getContentPane().setBackground(
newColor);
}
插件應該使用 Platform 的 Preference Store 機制存儲已配置的值,任何其他的插件也應該這麼做。 performOk() 方法可能看上去像這樣:
清單 7. performOk() 方法
public boolean performOk() {
getPreferenceStore().setValue("redValue", redSWTSlider.getSelection();
getPreferenceStore().setValue("greenValue", greenSWTSlider.getSelection());
getPreferenceStore().setValue("blueValue", blueSWTSlider.getSelection());
return true;
}
圖 4中顯示了從首選項頁面控制 Swing 編輯器的顏色。
圖 4. 從首選項頁面控制 Swing 編輯器的顏色
Workbench 知曉性
由於大多數編輯器最初被設計為獨立的 Java 應用程序設計,所以它們不注意 Workbench 的存在。它們可能不能處理 Platform 的一些環境屬性,這就限制了編輯器與 Platform 集成的親密度。為了給用戶提供一個更平滑更一致的開發體驗,開發者和插件供應商應該認真考慮增強他們現有的 Swing 工具從而使其變為 Workbench 知曉的。
例如,Ed 被編碼為直接處理基於文件系統的 Java 文件。因此,Platform 的 Java Project 和 Project Reference 與 Ed 無關。在這一部分中,我們將把 JButton添加到 Ed 以啟動一個 SWT 對話框,該對話框顯示了已編輯的 ThirdParty.java 被引用的項目。從用戶角度看,他單擊一個 Swing 小窗口,觸發了一個顯示特定於 Workbench 的信息的 SWT 窗口,這表示 Swing 編輯器、SWT 以及 Workbench 彼此正在緊密交互。
增強編輯器
假設您具有對 Ed 源代碼的訪問權,您可以添加額外的 Swing 小窗口以獲得額外的 Workbench 知曉性功能。將 JButton添加到編輯器的主內容窗格,然後它會啟動一個 SWT 對話框。將 JButton的文本設置為“Referenced Project”。
Referenced Project 按鈕的事件處理機制的工作方式將與 Save 按鈕(請參閱 雙向傳遞:將源代碼的更改返回到 workbench 中)的工作方式類似。插件實用程序類將偵聽來自這個按鈕的事件。實用程序類一接收到 Referenced Project 按鈕觸發的一個事件對象,它就會執行必要的操作來檢索項目引用信息並在 SWT 中顯示該信息。
檢索項目引用信息
在 SWT 對話框可以被顯示之前,插件需要弄明白包含已編輯的 ThirdParty.java 的項目引用了 Workbench 中的哪些項目。這是插件類的工作,而且它可以使用如清單 8 所示的一種方法,其中傳入該方法的字符串變量是項目的名稱:
清單 8. 檢索項目引用信息
private String[] getReferencedProjectArray(String arg) {
String[] projectNameArray = null;
try {
IProject[] referencedProjects =
ResourcesPlugin.getWorkspace().getRoot().getProject(
arg).getReferencedProjects();
int referencedProjectsLength = referencedProjects.length;
if (referencedProjectsLength == 0) {
projectNameArray = new String[1];
projectNameArray[0] = "none";
}
else {
projectNameArray = new String[referencedProjectsLength];
for (int i=0; i < referencedProjectsLength; i++) {
projectNameArray[i] = referencedProjects[i].getName();
}
}
return projectNameArray;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
SWT 對話框
Project Referenced SWT 對話到底應該會是什麼樣子要由插件 GUI 設計師決定。在這個示例中,一個帶有 List 對象的簡單的 SWT Shell(要顯示引用的項目)就足夠了:
清單 9. 帶有 List 對象的 SWT Shell
public class SWTProjectReferenceFrame implements Runnable {
private Shell shell;
private Display display;
Thread myThread;
public void run() {
open();
}
public void open() {
display = new Display();
shell = new Shell(display);
shell.setLayout(new org.eclipse.swt.layout.GridLayout());
shell.setText("Projects Referenced - SWT Frame");
shell.setSize(400, 400);
createListGroup();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
EditorintegrationPlugin.getDefault().getEd().repaint();
display.sleep();
}
}
myThread = null; // disposing the thread when the SWT window is disposed.
}
// Other methods appear here ...
}
方法 createListGroup() 准備了 List 對象並設置其內容以包含 projectNameArray(請參閱 檢索項目引用信息)。
清單 10. 准備 List 對象
private void createListGroup() {
Group listGroup = new Group(shell, SWT.NULL);
listGroup.setLayout(new GridLayout());
listGroup.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
GridData.HORIZONTAL_ALIGN_FILL |
GridData.VERTICAL_ALIGN_FILL));
listGroup.setText("listGroup");
List list = new List(listGroup, SWT.V_SCROLL);
list.setItems(projectNameArray);
}
根據啟動 SWT 對話框的方式,您可能需要在一個單獨的線程(如清單 10 中的 myThread 對象所指出的那樣)中執行 SWT 窗口以避免在 Swing 編輯器中的重繪制(repaint)問題。
圖 5中顯示了 Swing按鈕啟動一個 SWT 框架。
圖 5. 從 Swing 按鈕啟動一個 SWT 框架
結束語
這裡描述的這些技術提供了一個臨時的解決方案,它可以幫助您快速地將基於 Swing 的工具集成到 Eclipse Platform 中。但是,只要有可能,您就應該在現有的 Swing 小窗口上使用緊密集成的 SWT/JFace 組件。例如,編輯器應該用 Eclipse Platform 的 Preference Page 框架作為配置插件的中心入口點,而不是用各個引用對話框框架來處理多個用戶引用。
盡管本文中的這些概念相對簡單且易於實現,但是請不要將 Swing 小窗口作為永久設備留在插件中。要控制和利用 Eclipse 項目中的所有服務,您就應該逐漸減少插件中陳舊的 Swing 代碼的數量以便支持 Eclipse 項目提供的各種框架。