学习如何将独立的、基于
Swing 的编辑器作为插件集成到 Eclipse Platform 中。通过使用一些简单的技术,您就可以在 Swing
工具、Eclipse Platform 和各种 SWT 小窗口(widget)之间共享资源,而且这些资源可以通过相互知晓性(mutual
awareness)通信。工具供应商如果打算将基于 Eclipse 的开发工具引入市场,又想最低限度地重新编码,也将发现本文有所帮助。
引言
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
项目提供的各种框架。 |