翻译自下面文章。还没有完成翻译,因为最近没有精力做这个事情,又看到很多朋友遇到这方面的问题,先发上来。
http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html
By Marc R. Hoffmann, Mountainminds GmbH & Co. KG, [email protected]
April 14, 2006
图片挂掉了,尽快修复。
摘要:
工作台提供的选择服务( Selection Service )允许工作台窗口中的不同部件( parts )之间进行有效的沟通。了解和使用现存的选择机制可以让你的插件有清楚的设计,有机地使他们和工作台结合,并向将来可能的扩展开放。
Eclipse 工作台对于 IDE 和其他应用程序都是一个强大的 UI 框架。它为一个高度完整和可扩展的用户界面提供了很多服务。一个典型的综合应用就是那些在工作台窗口中,显示特定对象的额外信息,并且当选择的对象发生变化的时候自动更新相关信息的视图。例如,属性视图这样工作:无论在工作台窗口的哪个部件,一个元素被选中的时候,属性视图会自动显示这个元素的属性。
其他应用,比如全局操作的“ enable ”状态的转换也依赖于当前的选择。
插件可以使用所说的“选择服务”而不必实现一个紧耦合的通信机制。它可以减少有可以选中的条目的部件和其他对选择变化起作用的部件之间的耦合。
本文概述了选择服务的机制和用法。
每个工作台窗口有它自己的选择服务的实例。这个服务追踪当前活动部件,并且向所有的注册的监听者传播选择改变事件。这种事件在当前布局中的选择改变或者当另外一个部件被激活的时候发生。这两种情况都可以由用户界面或者程序来触发。
元素或者文本被选中的视图不需要知道谁在关心这个选择。于是我们可以创建一个依赖于已经存在的视图中的选择的新视图而不必更改原视图中的任何一行代码。
下一节我们将介绍谁 (who) 为谁 (whom) 提供什么样 (what) 的选择。
从用户视图角度来讲,一个选择是指在表或者树控件中,高亮的实体的一个集合。一个选择还可以指在文本编辑器中的一段文本。在表象的背后,每个可视元素在工作台中都由一个 Java 对象描述。 JFace 的 MVC 实现映射了领域模型和直观表示。
在内部,一个选择是一个保留了对应于工作台中选择的图形化的元素的模型对象的数据结构。前面已经指出,共有两种基本的选择类型:
一个对象列表
一段文本
每个选择都允许为空,比如一个空列表或者长度为 0 的字符串。在 Eclispe 体系中,这些数据结构由下列接口定义:
IStructuredSelection 代表了一个对象集合; ITextSelection 和 IMarkSelection 描述了一段被选中的文本。
为方便使用,这些接口都有着默认的实现:
org.eclipse.jface.viewers.StructuredSelection
org.eclipse.jface.text.TextSelection
org.eclipse.jface.text.MarkSelection
这些实现被用在查看器( viewer )内部实现来吧底层的 SWT 事件转换为 ISelection 对象。这些实现在当元素需要以程序的方式被选中的使用也非常有用:
ISelection sel = new StructuredSelection(presetElement);
treeviewer.setSelection(sel);
所有的 JFace 查看器都是所谓的 选择提供者。选择提供者实现 ISelectionProvider 接口:
不同的 JFace 查看器使用和传播不同类型的选择:
Viewer Selection Type
ComboViewer IStructuredSelection
ListViewer IStructuredSelection
TreeViewer IStructuredSelection
+- CheckboxTreeViewer IStructuredSelection
ableViewer IStructuredSelection
+- CheckboxTableViewer IStructuredSelection
extViewer ITextSelection , IMarkSelection
+- SourceViewer ITextSelection , IMarkSelection
+- ProjectionViewer ITextSelection , IMarkSelection
自定义的查看器也可以作为选择提供者并且实现 ISelectionProvider 接口。
任何一个包含了查看器的工作台部件都应该把这个查看器注册为各自的 view site 的选择提供者:
getSite().setSelectionProvider(tableviewer);
即使你暂时没有需要马上传播你的选择,但这让你的插件向你或者其他人的插件实现开放。如果你的视图定义了一个依赖于当前选择的操作,那么这些操作的动态改变 enable 状态也需要你设置一个选择提供者。
工作台窗口典型地是由很多部件组成的,每个部件至少有一个查看器(这点 ms 不太对)。工作台保持追踪工作台窗口中当前选中的部件以及部件中的选择。这样,插件实现就可以访问这些信息或者注册选择变化的通知。
每个工作台窗口都有一个 ISelectionService 的实现,用来追踪当前的选择。一个视图部件可以通过他的 site 来获取这个对象的引用:
getSite().getWorkbenchWindow().getSelectionService()
选择服务知道当前活动部件或者指定 ID 的部件的当前选择:
ISelection getSelection()
ISelection getSelection(String partId)
典型的情况是视图作用于工作台窗口中的选择变化。这种情况下,最好注册一个 ISelectionListener 以在窗口的选择变化的时候获得通知:
void addSelectionListener(ISelectionListener listener)
void removeSelectionListener(ISelectionListener listener)
通过这种途径注册的监听者会在当前活动的部件的选择发生变化或者不同的部件被激活的时候获得通知。如果程序只对某个特定的部件的选择(并且不依赖于其真实的活动状态)感兴趣,那么可以只向特定 id 的部件注册监听者:
void addSelectionListener(String partId, ISelectionListener listener)
void removeSelectionListener(String partId, ISelectionListener listener)
即使当前没有给定 id 的部件,这种方式依然有效。一旦这个 id 的部件被创建,它的初始选择就会被传播给注册了的监听者。如果监听者实现了 INullSelectionListener 接口的话,监听者会在被监听的部件 dispose 的时候收到一个 null 选择。
ISelectionListener 是一个只有一个方法的简单接口。一个典型的实现是向这样的:
private ISelectionListener mylistener = new ISelectionListener() { public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) { if (sourcepart != MyView.this && selection instanceof IStructuredSelection) { doSomething(((IStructuredSelection) selection).toList()); } } };
根据你的需求,你的监听器实现也许需要处理上面代码片段中的几个问题:
1. 如果我们还需要提供选择服务,那么我们应该把我们自己的选择事件排除在外,不加处理。这避免了当用户在我们的部件中选择了元素的时候的不可预料的结果。
2. 检查我们是否可以处理这种选择
3. 取得选择内容并且进行处理
注意:不要弄混 ISelectionListener 接口和 JFace 查看器用来通知选择更改的 ISelectionChangedListener 。
当你不能再处理事件的时候(例如当你的视图被关闭),不要忘记删除你的选择监听器。 dispose() 方法是一个删除监听器的好地方:
public void dispose() {
ISelectionService s = getSite().getWorkbenchWindow().getSelectionService();
s.removeSelectionListener(mylistener);
super.dispose();
}
到现在为止,我们关注了选择服务的核心机制,覆盖了大多数用例。但在实际的实现当中,还有其他更多的问题会出现。
当浏览视图的时候,选择会频繁变化——尤其当使用键盘来滚动一个很长的列表或者使用鼠标拖动选择一段文本的时候。这会导致很多不必要的被注册为监听器的视图的更新,也可能导致你的程序响应很慢。
所谓的后选择事件会在一个较短短的延时以后再被发出。在这段延时时间内发生的选择会被忽略;仅仅最终的那个选择会被传播。 ISelectionService 有另外的方法来向延时选择事件注册监听器:
void addPostSelectionListener(ISelectionListener listener)
void removePostSelectionListener(ISelectionListener listener)
void addPostSelectionListener(String partId, ISelectionListener listener)
void removePostSelectionListener(String partId, ISelectionListener listener)
为避免性能问题,查看器一般应该用这种方式注册监听器。
选择提供者负责发送延时选择事件,它必须实现 IPostSelectionProvider 这个接口来支持延时选择事件,所有的 JFace 查看器都支持延时的选择事件。
ISelectionListener 中定义的 selectionChanged() 这个回调方法可以像源部件( originating part )传入一个参数一样得到新的选择项:
public void selectionChanged(IWorkbenchPart part, ISelection selection);
接口 INullSelectionListener
扩展了 ISelectionListener
,但是没有声明另外的方法。这是一个纯粹的标记接口,仅仅是为了标识那些即使是在选择为 ISelection 参数为 null 的情况下也想被通知的 selectionChanged() 方法的实现者。这个在当你想知道因为当前没有人提供一个选择项所以没有当前选择项的情况下非常有用。在下面情况下你会进入到这里面:
当前活动部件没有设置选择提供者。
我们为指定部件注册了监听器,但这个部件没有提供选择提供者。
工作台窗口没有活动部件,所有的部件都关闭了。
我们为指定部件注册了监听器,但这个部件被关闭了。
如果你仔细研究工作台 API 你会发现有两个选择服务: IWorkbenchPage
是一个 ISelectionService
。另外, IWorkbenchWindow
也有一个方法 getSelectionService() 。所以举例来说,有两种方法可以在一个部件中注册一个监听器:
getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(l);
或者
getSite().getPage().addSelectionListener(l);
实际上这两种方式是完全等价的,以为在 Eclipse2.0 以后,一个工作台窗口限制只有一个工作台页面。但是不要再添加和删除监听器的时候混合使用它们(例如用第一种方法添加,而用第二种方法删除),因为在内部他们是两个不同的实现。
要明白,工作台部件的 site 只接受一个选择提供者,而且必须只能在 createPartControl() 方法中调用:
getSite().setSelectionProvider(provider);
工作台不支持在工作台部件的运行过程中替换选择提供者。如果一个部件包含了多个提供选择项的查看器,例如“ Java Hierarchy ”这个视图,必须提供一个中间的 ISelectionProvider 的实现来允许动态地在部件中委派给当前活动的查看器。作为一个开始,你可以查看本文提供的 SelectionProviderIntermediate.java
这个文件。
本文主张选择服务要负责帮助减少视图之间选择引起的不必要的相应。但是处理一个选择项的视图还必须为提供任何有用的功能而处理选择的对象。