http://www.ibm.com/developerworks/cn/opensource/os-eclipse-emf/
代码见outlook邮件
使用 Common Navigator Framework 和 Eclipse Modeling Framework 操作和浏览基于 EMF 的模型中的内容
简介: 通过本文,了解如何使用 EMF.Edit 和 Common Navigator Framework (CNF) 创建基于树形查看器的模型导航插件。构建一个 Eclipse 插件,使用户能够操作和浏览基于 Eclipse Modeling Framework (EMF) 的模型的内容。文中对开发插件提供了分步指导,实现了可通过 EMF 编辑框架提取模型内容的适当结构,并在基于 CNF 的视图部件显示内容。
通常,EMF 项目资源 — 如 EMF Ecore 模型 — 作为查看器中的单个对象进行显示(见 图 1 的左侧)。这种显示方法的限制是,我们在研究模型时必须要打开相应的编辑器。这一问题在依赖域模型进行开发时显得尤为突出。有一种解决方法是,构建自定义的视图,使我们能够访问所需的域模型内容。我们可以从头开始构建此插件,也可以使用现有的框架减轻开发负担。下面将分别对创建此类插件的过程进行概要以及详细的介绍。在本文最后,我们将拥有一个查看器插件,用于浏览 Ecore 模型(见图 1 右侧)。
图 1. 导航器的类型
我们已经了解了此 ModelNavigator 的用途,但在开始开发之前,需要对即将使用到的 Eclipse 组件有一个基本的了解。我们的任务是,构建一个树形的查看器用以显示模型的层次结构。树形查看器的详细信息不在本文的讨论范围之内;请参阅参考资料 了解更多细节。一个需要注意的重要方面是,树形查看器通过称为内容提供程序的适配器访问模型对象,并使用标签提供程序确定显示对象的方式。下面的章节详细介绍了如何访问内容和标签提供程序的数据,以及如何显示模型导航器。
EMF.Edit 框架通常用于为 EMF 模型构建编辑器。为构建这些编辑器,框架提供了命令的代码生成功能和一些其他类,用于提供对模型的程序化访问。此框架所公开的另外一组功能 — 也是对我们而言最重要的一组功能 — 是它提供的一些类,使用这些类可以方便地在查看器中显示 EMF 模型。框架提供这种访问的方式是使用通用内容和标签提供程序,它们使用了适配器为特定类型的 EMF 对象显示模型。这些类有AdapterFactoryContentProvider
和AdapterFactoryLabelProvider
类,这些类通过委托了解如何浏览 EMF 模型的项目提供程序适配器,为 EMF 对象提供查看器的对象、标签和图像。此方案在图 2 中进行了阐释。这种方案对我们的项目极其有用,因为它让我们不必去了解如何调整模型以适应视图。我们只需委托这些通用提供程序就可以了。
图 2. 从 Eclipse 帮助改编的 EMF.Edit
框架包含了用于各种 EMF 模型类型的项目提供程序。但是,并非大多数人都希望为每种模型类型构建单独的导航器。我们希望从同一个导航器中访问所有的模型内容。此时就出现了组合适配器工厂。它让我们能够显示来自多个 EMF 模型的对象,具体做法是,提供一个可从多个模型中适配对象集合的适配器工厂。
ComposedAdapterFactory
类是另一个 EMF.Edit 便利类,用作其他适配器工厂的通用接口。我们即将了解到,这个类的优势在于,导航器只需将我们所需要的模型类型的项目提供程序作为组合适配器工厂的一部分包括进来。然后,组合适配器工厂直接将实现委托给这些其他的提供程序。例如,我们可以让导航器显示生成器模型、Ecore 模型、UML 模型等。当查看器试图显示这些模型时,内容和标签提供程序将直接委托给适配器工厂,后者接着委托给适当的项目提供程序,从而使我们的开发更加轻松,并扩展 ModelNavigator 以支持多个域模型。
我们已经拥有了在 ModelNavigator 中访问模型内容的整个框架,现在所需的另一个组件就是放置导航器的实际的查看器。您可以扩展视图插件并实现自己的以树形视图显示所需内容的视图部件。但是,现有的框架就可以将各个域中的内容组合到一个视图中,使用户能够在查看器中操作和浏览编辑器模型。这种框架在 Eclipse V3.2 中作为 CNForg.eclipse.ui.navigator
引入,并允许开发者为一个导航器贡献内容、标签、动作、过滤器,以及其他功能。这就提供了一种集成导航的查看器的方式,并提供了统一的用户体验。
CNF 支持用于所有编辑器模型集成程序的查看器,支持非资源驱动的模型内容,并允许用户选择希望在集成的查看器中显示的内容。org.eclipse.ui.navigator.resources
插件就是使用此框架的一个示例,它使用 Project Explorer 视图的形式。 它为IResource
模型提供了声明性的查看器扩展。此框架为我们提供了构建查看器的最快速方法,它处理了查看器实现的细节,并且只需要我们实现模型的内容和标签提供程序。它还为将插件扩展至其他模型内容提供了空间,以及在查看器中使用更高级的框架功能(例如,分类器、过滤器、拖放等。)。
现在可以开始查看这些组件的运作,并开始创建模型导航插件。
创建 ModelNavigator 分为两个步骤进行。首先,我们将设置插件的基本结构并定义其行为。然后扩展这个简单的插件以显示 EMF 模型内容。
创建 ModelNavigator 的第一步是创建插件项目。为此,我们使用了新的项目向导:File > New > Project,选择Plug-in Project,然后单击Next。
我们将插件项目命名为 ModelNavigator
,设置其 ID 为 com.ibm.navigator.example.modelnavigator
,激活程序为com.ibm.navigator.example.modelnavigator.ModelNavigatorPlugin
,然后单击Finish。
图 3. 创建插件项目
现在开始开发插件。第一步是创建放置导航器的视图。为此,我们对插件进行必要的扩展以创建视图部件和类别,视图即显示在该类别下。在插件的 xml 文件中,我们将添加一个视图扩展。
在 Extensions 选项卡中:
- 单击 Add
- 在 Extension points 选项卡下面,选择 org.eclipse.ui.views
- 创建类别:
- 右键单击 org.eclipse.ui.views 扩展
- 选择 New > Category
- 将名称设为
com.ibm.navigator.example.modelnavigator.mncategory
- 将 ID 设为
Model Navigator
- 创建视图部件:
- 右键单击 org.eclipse.ui.views 扩展
- 选择 New > View
- 将 ID 设为
com.ibm.navigator.example.modelnavigator.mnview
- 将名称设为
Model Navigator View
- 将类设为
org.eclipse.ui.navigator.CommonNavigator
- 并类别设为
com.ibm.navigator.example.modelnavigator.mncategory
- 保存对项目的修改
如前所述,我们将一些实现工作转移给了 CNF。关于设置视图的方式需要注意的一点是,我们并没有创建自己的类来扩展视图的 ViewPart
,而是以视图使用通用导航器类的方式来实现。这也意味着我们在类字段中指向的通用导航器类需要被添加到插件的类路径中。因此,继续操作并将org.eclipse.ui.navigator
添加到 Dependencies 选项卡上所需的插件中。
下一步是配置插件的 CNF 属性,该属性用于指定导航器的默认行为。
首先,我们将视图与通用导航器查看器关联。在 Extensions 选项卡中:
- 单击 Add
- 在 Extension points 选项卡下面,选择 org.eclipse.ui.navigator.viewer 并单击Finish
- 创建查看器:
- 右键单击 org.eclipse.ui.navigator.viewer 扩展
- 选择 New > viewer
- 将 viewerid 设为我们前面定义的视图 ID(
com.ibm.navigator.example.modelnavigator.mnview
)
导航器插件的行为由我们给它绑定的内容、动作,以及使用 CNF(过滤器、分类器等)时插件中设置的其他选项进行管理。在插件中,包含元素选择哪些扩展对查看器可见。内容扩展告诉框架如何在视图中显示内容,而动作扩展告诉框架哪些选项对视图(上下文菜单等)可用。为使这个粗略的导航器正常运作,需要添加一些IResource
模型中的内容和动作绑定,为插件提供 Package Explorer 所熟悉的风格。为此,我们为导航器创建了viewerContentBinding
以添加资源内容扩展,还创建了 viewerActionBinding
以添加资源操作扩展。
要添加 IResource
内容扩展,在 Extensions 选项卡中:
- 右键单击 org.eclipse.ui.navigator.viewer 扩展
- 选择 New > viewerContentBinding
- 将 viewerid 设为插件的视图部件 ID(
com.ibm.navigator.example.modelnavigator.mnview
) - 添加内容绑定:
- 右键单击先前创建的 viewerContentBinding
- 选择 New > includes
- 右键单击先前创建的包含元素
- 选择 New > contentExtension
- 将模式设为
org.eclipse.ui.navigator.resourceContent
要绑定 IResource
动作扩展,在 Extensions 选项卡中:
- 右键单击 org.eclipse.ui.navigator.viewer 扩展
- 选择 New > viewerActionBinding
- 将 viewerid 设为插件的视图部件 ID(
com.ibm.navigator.example.modelnavigator.mnview
) - 添加动作绑定:
- 右键单击先前创建的 viewerContentBinding
- 选择 New > includes
- 右键单击先前创建的包含元素
- 选择 New > actionExtension
- 将模式设为
org.eclipse.ui.navigator.resources.*
此时,我们已经将导航器的结构设置完毕。我们了解了如何告知导航器框架我们希望显示的内容类型,以及希望导航器如何运作。虽然我们并非一定要添加这些扩展,但是添加
模型内容和动作后,我们的视图会更有用些。现在我们还了解了如何将内容绑定到查看器,还可以扩展它以添加自定义或预定义的过滤器。请参阅参考资料 以了解有关这些功能的更多信息。这个导航器仍然不是我们所需要的,因此我们要添加在查看器中显示 EMF 模型的功能。
IResource
要在导航器中显示 EMF 域模型的内容,我们需要告知框架需要哪个模型,如何获取此模型中的信息,以及显示信息的时机/方式。这里涉及创建一个内容扩展,此扩展可被导航器内容服务用于显示 EMF 域模型。通过创建org.eclipse.ui.navigator.navigatorContent
插件的扩展可完成此工作。
导航器内容扩展定义了内容提供程序和标签提供程序,可以使用这些程序为 EMF 模型的元素提供子对象和父对象。该扩展还定义了何时调用此扩展提供子对象
或父对象
triggerPointspossibleChildren
。我们还可以修改导航器内容的很多其他属性以更改插件的行为,如动作提供程序、通用向导、过滤器等。但这些属于附加特性,对于我们创建 ModelNavigator 插件而言不是必需的。
首先,我们需要创建新的内容扩展,在其中指定通用导航器使用的内容和标签提供程序,稍后我们将实现这些类。
要添加 navigatorContent
扩展,在 Extensions 选项卡中:
- 单击 Add
- 在 Extension points 选项卡下面,选择
org.eclipse.ui.navigator.navigatorContent
并单击Finish - 创建导航器内容:
- 右键单击 org.eclipse.ui.navigator.navigatorContent 扩展
- 选择 New > navigatorContent
- 将 ID 设为
com.ibm.navigator.example.modelnavigator.emfModelContent
- 将名称设为
EMF Model Content
- 将内容提供程序设为
com.ibm.navigator.example.modelnavigator.MNViewContentProvider
- 将标签提供程序设为
com.ibm.navigator.example.modelnavigator.MNViewLabelProvider
- 将优先级设为
normal
- 将 activeByDefault 设为
true
我们所实现的类接下来将会用作此导航器内容的内容提供程序和标签提供程序。
我们将使用 EMF.Edit 框架执行内容提供程序和标签提供程序的实际工作。因此,需要创建的内容提供程序类和标签提供程序类应为 EMF 编辑框架中适配器工厂的子类。在创建内容提供程序类和标签提供程序类 —— 它们分别委托给AdapterFactoryContentProvider
和AdapterFactoryLabelProvider
—— 之前,我们应记住,这些适配器工厂是使用一系列的项目提供程序为不同的 EMF 模型所实例化的。这意味着我们需要首先创建这些提供程序作为组合的适配器工厂。创建一个新类(File > New > Class)MNComposedAdapterFactory
.
图 4. 创建实现类
我们还需要将 org.eclipse.emf.codegen.ecore.ui
添加到 Dependencies 选项卡上所需的插件中。这使插件可以使用不同 EMF 模型类型的ComposedAdapterFactory
项目提供程序和其他的 EMF.Edit 便利类。然后,MNComposedAdapterFactory
类的实现将如清单 1 所示。
清单 1. ComposedAdapterFactory 类
|
需要注意的重要一点是,我们创建了一个静态方法用于创建一系列的项目提供程序。在这些提供程序中,我们希望适配器工厂委托给 EcoreItemProviderAdapterFactory
,以便为 Ecore 模型提供内容和标签。
使用类似的方法创建我们在 navigatorContent
扩展详细信息中指定的内容和标签提供程序类。
从内容提供程序开始(MNViewContentProvider
),单击扩展详细信息面板中提供程序名旁边的链接创建类,或使用 File > New > Class 对话框创建类。我们没有为内容提供程序实现 ITreeContentProvider
接口,而是使用 EMF.Edit 类。为此,更改MNViewContentProvider
类,使其扩展AdapterFactoryContentProvider
类。AdapterFactoryContentProvider
类已经知道如何实现ITreeContentProvider
接口,实际上,它的实现方式是委托给一个适当的提供查看器内容的项目提供程序。
AdapterFactoryContentProvider
类的构造函数中需要一个适配器工厂。因此我们不能对内容提供程序使用隐式的构造函数。我们必须显式地声明内容提供程序构造函数并使用所需的参数调用超类构造函数。声明构造函数后,将super(MNComposedAdapterFactory.getAdapterFactory());
添加到其中。这个类中我们需要重点关注的其他方法有getChildren
、getParent
、hasChildren
和 getElements
。
查看器需要显示域对象的子元素时将调用 getChildren
方法。它将返回一组域对象,这些域对象是参数元素的子元素。与此类似,调用getElements
方法可以获得参数元素的域对象。这二者的运作原理类似;只是调用的情形不同。要实现getChildren
方法,我们只需要求AdapterFactoryContentProvider
返回给定父元素 URI 的子元素。getChildren
方法如清单 2 所示。对于getElements
方法,我们只需返回对这个getChildren
方法的调用。
清单 2. getChildren 实现
|
为在树形视图中显示内容,下面两个重要步骤将确定视图中的对象何时拥有下述条件的子对象:需要进行显示并且能够将一组子对象关联到父对象。这就使查看器能够控制域对象的状态,而不用考虑域对象是否展开或是被破坏。在本文的例子中,可将它转换为拥有一组子包的 Ecore 模型。这些包中的类对象与相同的父包相关联。我们在清单 3 中实现了这一功能。
清单 3. getParent 实现
|
这里,我们可能看见一些错误,即,无法解析当前使用的资源。为了解决这个问题,将 org.eclipse.core.resources
添加到 Dependencies 选项卡上所需的插件中。另外,为了使内容提供程序获取视图中文件资源的 URI,我们需要使用平台中的资源集。我们创建了单个的静态资源集实现,使用该实现将private static ResourceSetImplresourceSet = new ResourceSetImpl();
添加到MNViewContentProvider
类中可获得这些资源。
标签提供程序与内容提供程序具有相同的格式,因此我们直接将工作委托给 EMF.Edit 框架中的 AdapterFactoryLabelProvider
类。与内容提供程序相同,标签提供程序接受域对象作为它的参数,但是后者所返回的Image
或String
应与查看器中的这个对象相关联。如果要对查看器再做些定制,可以通过编程控制何时希望委托获取名称和图标的任务,以及何时希望为对象提供自己的图表。出于本文讨论的目的,我们不做定制。我们只需要 EMF 模型所提供的图像和文本。要实现标签提供程序,我们使用为内容提供程序准备的项目提供程序列表调用超类,然后将所有对对象的图像或描述的调用委托给AdapterFactoryLabelProvider
超类。
清单 4. 标签提供程序
|
此时,我们已经在插件中完全实现了内容扩展。让视图使用内容扩展需要执行另外两个步骤。首先,我们必须定义事件以通知使用这个特殊的内容扩展。其次,我们必须将导航器内容绑定到视图。
通过将 <possibleChildren />
和 <triggerPoints />
元素添加到导航器内容扩展中,当能够显示类中所描述的域模型时,我们可以向 CNF 发出通知。这两个元素可以很容易地定义为插件 xml 文件中的 Eclipse 核心表达式。在本文的例子中,当查看器包含有作为 EMF 模型的实例的对象时,将调用导航器内容。因为目前我们只希望浏览 Ecore 模型,所以可以将其处理为一个简单的表达式,用于检查资源的扩展是否为 Ecore。可能得到的子元素规定了扩展何时为查看器中的对象提供父对象,在本文的例子中,这些对象指的是 EMF 模型对象或资源的实例。
清单 5. 触发点和可能的子元素
|
最后,将我们创建的导航器内容绑定到视图,我们将使用先前同样的方法将 IResource
模型内容扩展包含到查看器内容绑定。
要将导航器内容 com.ibm.navigator.example.modelnavigator.emfModelContent
绑定到实际视图,在 Extensions 选项卡中执行以下操作:
- 扩展 org.eclipse.ui.navigator.viewer 元素
- 扩展 viewerContentBinding 元素
- 创建新的内容扩展:
- 右键单击 includes 元素
- 选择 New > contentExtension
- 将模式设为先前定义的
navigatorContent
(com.ibm.navigator.example.modelnavigator.emfModelContent
)
- 单击 Save
现在,将 EMF 模型内容添加到通用导航器中的步骤已基本结束。我们现在拥有的 ModelNavigator 插件应该可以显示来自 IResource
模型和 EMF Ecore 模型中的对象。
我们需要运行对象并在新视图中显示 Ecore 模型以确保 ModelNavigator 可以正常工作。对于本测试,我们需要创建在 Model 导航器中显示的 EMF 对象。我们将使用基于SchoolLibrary
UML 文件的示例(请参阅参考资料)。
将 ModelNavigator 项目作为 Eclipse 应用程序运行,然后:
- 在新的工作台中,单击 Window > Show View > Other 显示 ModelNavigator 视图
- 找到 Model Navigator 类别并单击 Model Navigator View
- 单击 OK
要在运行时工作区中创建 EMF 项目,请执行以下步骤:
- 单击 File > New > Project 创建新项目
- 扩展 Eclipse Modeling Framework 元素
- 单击 EMF project 并选择 Next
- 将项目命名为
SchoolLibrary
并选择 Next - 选择 Rose class model 作为模型导入程序并选择 Next
- 对于模型 URI,浏览到下载 schoollibrary.mdl 文件的位置
- 单击 Next
- 选择 library 和 SchoolLibrary 包,如图 5 所示。
- 单击 Finish
图 5. 创建 EMF 测试项目
如果一切按预想进行,我们应该可以在 Model Navigator 视图中看见新的 SchoolLibrary
项目。更重要的是,如果导航器按预期情况运行,我们就能够扩展项目的模型目录,并且能够浏览导航器视图中的library
和SchoolLibrary
Ecore 文件的内容。导航器应类似于图 6 所示。
图 6. 测试 ModelNavigator
我们已经完成了设定的任务:在查看器中显示 EMF 模型内容。但是,当我们真正地在编辑器中打开 Ecore 文件并开始进行更改时会出现什么情况?就目前而言,什么也不会发生。ModelNavigator 对于针对其当前显示的模型做出的更改没有反应,也不会刷新。要让它刷新当前显示的模型,我们必须让查看器意识到编辑器中做出了更改,并强制刷新。实现这种同步的一种方法是将资源更改侦听程序添加到内容提供程序中。下面的章节简要介绍了所需的步骤。
我们让内容提供程序响应模型中的更改的方法是,允许其侦听模型文件资源中的更改(本例中指的是 Ecore 文件)。为此,我们需要在 MNViewContentProvider
类中实现IResourceChangeListener
和IResourceDeltaVisitor
接口。
资源发生更改时调用 resourceChanged(IResourceChangeEvent event)
方法,并传递描述资源更改的一组事件。这组更改为我们提供了在不同时间点资源树的差别。我们可以使用它查看资源树中的更改,并根据已更改的资源类型确定所需的操作。我们实现resourceChanged(IResourceChangeEvent event)
方法,如清单 6 所示,它为我们提供了已更改的资源集,并允许我们访问资源的变化。
清单 6. 资源更改的方法
|
更改被访问后,调用 visit(IResourceDelta delta)
方法。对于我们的插件,我们只关注已更改的资源是拥有 Ecore 扩展的 IResource 文件的情形。如果更改的资源是 Ecore 模型,那么需要刷新 ModelNavigator 以反映这些更改。为此,我们提取更改后的文件,获取其资源,然后重新加载此资源所表示的模型。
清单 7. 访问方法
|
要使用此资源更改侦听程序,我们需要将其添加到内容提供程序中,在适当的时候删除。在 MNContentProvider
类中,通过在构造函数中插入ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
添加侦听程序。另外,将ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
插入到类的处理方法中。虽然这样应该可以处理对资源更改的更新,但是请记住这是一种处理更改的简单方法。通过只更新内容提供程序中受影响的模型,我们将不查看此模型的依赖项以更新这些元素。这是保持导航器更新的一种基本方法。
要测试 Ecore 模型中的更改在查看器中得到了反映,我们将只需重新运行应用程序并开始做出更改即可。
将 ModelNavigator project 作为 Eclipse 应用程序运行,然后:
- 浏览到模型目录下的 schoollibrary.ecore
- 在编辑器中打开 schoollibrary.ecore 文件
- 展开 schoollibrary.ecore 节点
- 右键单击 SchoolLibrary 包节点
- 选择 New Child > EClass
- 在 Properties 选项卡上,将此新类命名为(
Test
) - 保存文件
此项更改应触发了资源更改侦听程序代码的执行并强制 ModelNavigator 更新模型。我们应该看见 ModelNavigator 视图重新加载自身。如果我们扩展树并浏览到 schoollibrary.ecore 文件,我们应该看见所做的更改,如图 7 所示。
图 7. 进行资源更新的 ModelNavigator
对 ModelNavigator 实现的最后一个 — 也是非常简单的一个 — 扩展是使用 Composed Adapter Factories。我们所创建的导航器将显示 Ecore 模型,但是其他类型的 EMF 模型怎么办?这实际上是一项非常简单的任务,得益于 EMF.Edit 框架的实现方式。我们可以以最小的更改添加对更多域模型的支持,具体做法是,将合适的适配器工厂添加到ComposedAdapterFactory
并为这些新模型设置触发器。
例如,要将 genmodel
对象添加到导航器中,我们只需将生成器模型项目提供程序添加到内容提供程序和标签提供程序所使用的适配器工厂列表中。在MNComposedAdapterFactory
类中,将factories.add(newGenModelItemProviderAdapterFactory());
添加到createFactoryList()
方法中。模型导航器需要更改的惟一一处是告诉导航器内容何时触发此内容提供程序。我们使用与触发点相同的资源扩展检查策略,如下所示。
清单 8. genmodel 的触发点
|
通过将这个新触发器表达式添加到当前触发器的正下方,我们在这些情况下使导航器内容相关:对象是 IResource
的实例且拥有一个 Ecore 扩展,或者对象是IResource
的实例且拥有一个genmodel
扩展。如果我按上面介绍的方法重新运行此项目,我们将发现现在可以浏览 Ecore 和 Generator EMF 模型(见图 8)。但是,这种功能在我们的简单资源更新方案中并没有提供,因此更新在 ModelNavigator 视图中的不同模型之间并不同步。
图 8. 具有多个模型的 ModelNavigator
我们已经完成创建插件以便在查看器中显示 EMF 模型内容的任务。我们通过使用 Eclipse 提供的框架展示了如何简化此过程,并进行了进一步的探讨,即,将一些简单的特性添加到插件中。本文只研究了这些框架所公开的功能中的一部分,我强烈建议所有读者都深入了解一下这些 Eclipse 组件。