如果 Eclipse 中的默认插件模板能够满足用户的需要,它们会非常有帮助。但是,如果需求超出了默认模板的范围,就需要定制模板。本文讨论如何定制 Eclipse 中的插件项目模板,调整模板中的多区段实现和控制,通过定制 UI 组件提高易用性,在 UI 端添加输入检验功能,解释如何自动地组织插件项目的目录结构。
如果您曾经在 Eclipse 中创建过插件项目,那么应该熟悉现有的插件项目模板,这些模板可以为新项目提供方便的起点。现有的插件模板可以节省大量时间,但是它们并不是万能的。
使用插件模板的一个难题是找到满足用户需求的模板。但是,模板只提供数量有限的功能,而用户的需求各不相同,创建模板的开发人员几乎不可能事先预测出所有需求。在这种情况下,定制模板是一种简便的方法,这可以为用户提供所需的插件,又能够避免从头编写插件。
在本文中,学习:
- 如何定制 Eclipse 中的插件项目模板。
- 模板的高级特性,比如多区段实现和控制、UI 组件定制和检验。
- 自动地组织项目目录结构的工具。
先决条件
本文是为熟悉 Eclipse 并对构建插件感兴趣的 Java™ 开发人员撰写的。本文假设读者基本了解插件和基于 Eclipse 的开发工具。为了构建插件示例,您的计算机上需要安装 Eclipse(V3.4 或更高版本)和 Java Runtime Environment (JRE)。
这里的内容基于 developerWorks 文章 “使用 Eclipse 插件开发环境构建模板”。如果您没有使用过插件模板,建议您先阅读这篇文章,它介绍了如何创建模板。因为这篇文章是入门级的,它介绍的模板缺少本文讨论的高级特性,包括:
- 多区段实现和控制
- 在一般情况下,插件模板的各个区段定义为功能模块,其中包含已经建立的或根据用户输入生成的相互依赖的文件。一个区段中的文件要么都复制到目标项目中,要么都不复制。用户可以把一个区段定义为必需的或可选的。必需区段中的文件必须包含在插件项目中,而可选区段中的文件应该根据选择复制。如果插件模板提供功能的超集,但是项目只需要功能的子集,多区段实现和控制会很有帮助。
- UI 组件定制
- Eclipse 为插件项目模板提供几个默认的 UI 组件。这些基本的 UI 组件只提供有限的功能。为了提高易用性和功能性,有时候需要定制复杂的组件。
- 输入检验功能
- 这可以增强数据检验控制。
- 自动地组织插件项目的目录结构
- 可以使用模板把插件项目中的资源组织成特定的目录结构。这可以减少手工工作量。
这些扩展特性可以提高插件模板的可伸缩性、易用性和效率。在下面几节中,我们要提供一个使用这些特性的模板示例。
示例研究:深入讨论插件模板
假设我们需要一组插件项目。它们用来构造各种 Eclipse 透视图,比如 Java 和 Web 透视图。一个项目用于一个透视图的开发。每个透视图可以有几个视图。每个视图有相关的资源,比如图像和自动生成的 Java 源代码文件,需要把这些资源复制到项目中。透视图中的视图数量和每个视图的名称由各个透视图决定。
定制插件模板是生成这些目标项目的好方法。
创建一个简单的模板
首先,创建一个新的插件项目(File > New > Project > Plug-in Project)。一定要选择 This plug-in will make contribution to the UI 复选框。这是因为此示例中的目标项目都用于透视图开发,都要使用 UI。创建新项目之后,还应该在 plugin.xml 中添加一个扩展 org.eclipse.pde.ui.plugin.Content 的模板向导扩展。
新特性 A:多区段实现和控制
仔细研究此示例的需求,就会发现在定制模板方面的一个问题。因为每个透视图的视图数量是可变的,所以在视图数量确定之前,不可能生成相关资源并复制到目标项目中。
为了解决此问题,我们来考虑两个解决方案。一种常用的方法是为每个透视图类别创建一个模板,一个透视图类别中的所有透视图包含相同数量的视图。另一种方法更高效更高级。只为所有透视图类别创建一个模板。通过区段控制管理视图和透视图之间的链接。
添加用于透视图和视图区段的扩展
在此示例中,把所有资源分为两个部分。使用透视图区段管理透视图资源,使用视图区段管理视图资源。如果用户需要没有视图的透视图,就不会把视图资源复制到目标项目中。同样,如果用户需要包含 9 个视图的透视图,那么在透视图中注册 9 个视图,重复生成视图资源并把资源复制到目标项目中。
为了生成这两个区段,需要在 Manifest Editor 的 Extensions 选项卡中添加扩展。
图 1. Extensions 选项卡 — 透视图区段
图 2. Extensions 选项卡 — 视图区段
区段实现
图 3 给出模板项目的结构。在此示例中,创建 5 个新文件(SampleWizard.java、PerspectiveSection.java、ViewSection.java、$perspectiveClassName$.java 和 $viewClassName$.java)。它们构成项目的主要部分。
图 3. 模板项目结构
PerspectiveSection 和 ViewSection 是 OptionTemplateSection 的子类,这个类代表模板向导的一个区段。它们有三个主要功能:创建模板的 UI、保存从 UI 输入的变量和更新模板模型。表 1 解释区段类的各个方法。
表 1. 区段类的方法
方法名 | 功能说明 |
---|---|
initializeFields | 使用输入参数初始化向导页面上的选项。一些选项可能取决于用户在前面的向导步骤中做出的选择。 |
addPages | 把与模板相关的页面添加到向导中。 |
getStringOption | 获取选项名。 |
getSectionId | 返回区段 ID。 |
updateModel | 把必需的条目添加到插件模型中。 |
$perspectiveClassName$.java 和 $viewClassName$.java 是模板文件,它们会自动转换为目标项目的 Java 源代码文件。此示例中的模板文件非常简单。我们仅仅创建所有视图的列表,然后把这些视图添加到透视图中。
接下来,在向导行为中添加区段控制。SampleWizard 类是 NewPluginTemplateWizard 类的子类,这个类作为插件的向导模板。可以通过 SampleWizard 类把指定的区段分配给目标插件项目。performFinish 方法是从超类继承的。可以使用它在向导中执行特殊的最终处理,包括循环处理模板区段并依次执行它们,从而为目标项目生成文件。清单 1 显示如何在向导中注册区段和 performFinish 方法中的主要操作。
清单 1. SampleWizard.java
//register all related sections in wizard public ITemplateSection[] createTemplateSections() { return new ITemplateSection[] { new PerspectiveSection(), new ViewSection() }; } public boolean performFinish(final IProject project, IPluginModelBase model, IProgressMonitor monitor) { ... ITemplateSection[] sections = super.getTemplateSections(); //monitor finish action in this wizard monitor.beginTask("perform finish", sections.length); ... //get sections for (int i = 0; i <sections.length; i++) { if (sections[i].getClass().equals(PerspectiveSection.class)) { perspectiveSection = (PerspectiveSection) sections[i]; } else if (sections[i].getClass().equals(ViewSection.class)) { viewSection = (ViewSection) sections[i]; } } ... //set variables to sections and manage sections ... viewSection.setViewClassName(values[j]); viewSection.setSourcePath(sourceFolderName); viewSection.execute(project, model, new SubProgressMonitor(monitor, 1)); perspectiveSection.setSourcePath(sourceFolderName); perspectiveSection.setViewNames(viewNames); perspectiveSection.execute(project, model, new SubProgressMonitor(monitor, 1)); } |
新特性 B:UI 组件定制
正如前面提到的,透视图可以包含用户所需的任意数量的视图,用户可以设置每个视图的名称。为了收集一个透视图中的所有视图,我们设计了下面的输入面板。它有一个 View Class Name 框和一个 View List 框。用户可以输入视图类名,然后通过单击 Add 把它添加到视图列表中。用户还可以使?? Remove 按钮删除视图。
图 4. 定制的模板选项
为了创建这个面板,在模板项目中添加 ViewOption,它扩展 TemplateOption(见图 3)。我们使用它设置 UI 组件并存储用户的输入。清单 2 演示如何用一个视图列表和两个按钮定制 UI 组件。
清单 2. ViewOption.java
//add UI components in this panel public void createControl(Composite parent, int span) { ... //create View List listLabel = new Label(parent, SWT.LEFT); listLabel.setText("View List:"); listViewerField = new ListViewer(parent); listField = (List) listViewerField.getControl(); GridData listGridData = new GridData(GridData.FILL_HORIZONTAL); listGridData.heightHint = 100; listField.setLayoutData(listGridData); ... //create add button addButton = new Button(parent, SWT.PUSH); addButton.setText("Add"); ... addButton.setLayoutData(addBtnData); addButton.addSelectionListener(...); //create remove button removeButton = new Button(parent, SWT.PUSH); ... removeButton.addSelectionListener(...); } |
新特性 C:输入检验功能
输入检验功能是指在使用所有用户输入之前对它们进行检验的过程。输入检验功能对于应用程序的安全性是极其重要的。在我们的示例中,定义一条检验规则:对于一个透视图中的所有视图,视图类名必须是惟一的。如果违反此规则,就会在面板顶部显示一条错误消息。
图 5. 定制检验的模板选项
可以通过调用相应区段类中的 validateOptions 方法实现输入检验功能。这个方法是从超类继承的,可以通过覆盖它实现自己的检验功能。清单 3 给出此示例中的 validateOptions 方法。
清单 3. ViewSection.java 中的 validateOptions 方法
public void validateOptions(TemplateOption source) {
this.getPage(0).setErrorMessage(null);
ViewOption viewOption = (ViewOption) source;
Text textField = viewOption.getTextField();
List listField = viewOption.getListField();
String[] items = listField.getItems();
if (items != null && items.length > 0) {
//validation rule check
for (int i = 0; i < items.length; i++) {
if (items[i].equals(textField.getText())) {
this.getPage(0).setErrorMessage("Class name '"
+ textField.getText()
+ "' already exists in View List.");
break;
}
}
}
}
|
新特性 D:自动地组织插件项目的目录结构
我们的目标之一是让模板自动地组织目标插件项目的目录结构。我们通过三个步骤实现此目标:
- 使用变量存储目标项目的目录结构。
- 编写一个 Ant 脚本,它用于创建目标目录结构并把资源分配到适当的目录。
- 添加 IResourceChangeListener 以监视模板的工作空间。区段文件的生成过程完成之后,触发 Ant 脚本的执行。
实现
- 为了简化,我们假设所有 Java 文件都复制到一个包中,包名与项目名相同。例如,如果项目名为 com.ibm.template,那么所有 Java 文件都复制到 com.ibm.template 包中。对此过程的讨论超出了本文的范围,详细信息请参见 “使用 Eclipse 插件开发环境构建模板”。
- 清单 4 给出此示例中使用的 Ant 脚本。它包含三个 target。第一个 target 根据源代码路径和包路径创建一个目录结构。第二个 target 把所有 Java 文件复制到适当的目录。最后一个 target 从新的插件项目中删除这个脚本文件。
- 在 SampleWizard 类的 performFinish 方法中添加 IResourceChangeListener。清单 5 给出这个监听器的实现。
清单 4. CreatePackage.xml
<?xml version="1.0"?> <project name="template project" default="clean" basedir="."> <property name="package" value=".\$sourcePath$\$packagePath___FCKpd___3quot;></property> <target name="create"> <mkdir dir="$dollarMark${package}"/> </target> <target name="move" depends="create"> <move todir="$dollarMark${package}"> <fileset dir="."> <include name="*.java"/> </fileset> </move> </target> <target name="clean" depends="move"> <delete dir="."> <include name="CreatePackage.xml"/> </delete> </target> </project> |
清单 5. SampleWizard.java — 监听器
public boolean performFinish(final IProject project, IPluginModelBase model, IProgressMonitor monitor) { ... //get workspace final IWorkspace workspace = ResourcesPlugin.getWorkspace(); //get Ant file String antFilePath = "CreatePackage.xml"; final IFile file = project.getFile(antFilePath); //add Listener to monitor resource changing IResourceChangeListener listener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { try { if (event.getType() == IResourceChangeEvent.POST_CHANGE) { IResourceDelta rootDelta = event.getDelta(); IPath filePath = file.getFullPath(); IResourceDelta targetDelta = rootDelta.findMember(filePath); if (targetDelta != null) { URI uri = file.getLocationURI(); File antFile = new File(uri); //run Ant script Project project = new Project(); project.fireBuildStarted(); project.init(); ProjectHelper helper = ProjectHelper.getProjectHelper(); helper.parse(project, antFile); project.executeTarget(project.getDefaultTarget()); project.fireBuildFinished(null); workspace.removeResourceChangeListener(this); } } } catch (Exception e) { e.printStackTrace(); } } }; workspace.addResourceChangeListener(listener); ... } |
使用模板
创建了新的模板项目之后,需要刷新此项目。右键单击此项目并选择 Refresh 菜单项。这是因为我们的 Ant 脚本是在生成项目之后执行的。我们需要在 Eclipse IDE 中手工刷新此项目。图 6 是通过 “示例研究:深入讨论插件模板” 中讨论的模板示例生成的插件项目。
图 6. 新的项目结构
需要把这个新项目导出到 [eclipse_home]\plugins 目录,然后用 -clean 参数重新启动 Eclipse。在 Eclipse 启动之后,检查我们的透视图是否已经添加到透视图列表中(见图 7)。选择 myplugin perspective,然后单击 OK。这个透视图就会打开。
图 7. 透视图列表
结束语
读完本文之后,您应该对 Eclipse 插件模板有了更深入的认识。您学习了如何创建定制的输入组件、如何控制模板文件的生成以及如何用一个模板生成具有不同目录结构的插件项目。通过使用这些模板特性,可以更轻松地创建插件项目。(责任编辑:A6)