20.3 在视图之间实现事件监听
两个视图中的组件之间的互动,在开发插件时是经常碰到的问题。例如,在图20.3所示界面中,单击视图1列表的某项时,视图2的文本框显示相应字符。本节将实现此功能。
20.3.1 主动式
主动式就是在视图1的代码中获取对视图2的对象引用,然后将视图1中的对象主动地传给视图2。具体实现如下所示:
1.修改View1.java、View2.java
Eclipse通过plugin.xml来加载插件和插件中的扩展点(如视图扩展点),所以可以在View1.java中由id标识来取得视图2对象,具体语句如下:
IWorkbenchPage wbp = getViewSite().getPage();
IViewPart view2 = wbp.findView("cn.com.chengang.myplugin.View2");
得到了视图2的对象后,其他一切就容易了,先给出修改后View1.java如下:
public class View1 extends ViewPart {
public void createPartControl(Composite parent) {
Composite topComp = new Composite(parent, SWT.NONE);
topComp.setLayout(new FillLayout());
final List list = new List(topComp, SWT.BORDER);
list.add("中国");
list.add("美国");
list.add("法国");
//列表选择事件监听
list.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
//由IWorkbenchPage获得view2对象
IWorkbenchPage wbp = getViewSite().getPage();
IViewPart view2 = wbp.findView("cn.com.chengang.myplugin.View2");
if (view2==null) return; //避开空值
//将当前选择的列表项显示在文本框中
Text text = ((View2) view2).getText();
text.setText(list.getSelection()[0]);
}
});
}
public void setFocus() {}
}
然后将View2.java的文本框对象改成类的实例变量,并编写它相应的Setter/Getter方法:
public class View2 extends ViewPart {
private Text text;
public void createPartControl(Composite parent) {
Composite topComp = new Composite(parent, SWT.NONE);
topComp.setLayout(new FillLayout());
text = new Text(topComp, SWT.BORDER); //注意:text前的类型定义已去除
text.setText("我是text框");
}
public void setFocus() {}
//文本框text相应的Setter/Getter方法
public Text getText() {return text;}
public void setText(Text text) {this.text = text;}
}
2.总结
(1)在插件中IWorkbenchPage对象比较重要,这里再给出一种获得此对象的通用方法,不过它是获得当前活动的IWorkbenchPage对象。
Activator.getDefault().getWorkbench().getActiveWorkbenchWindow(). getActivePage();
(2)IWorkbenchPage.findView("cn.com.chengang.myplugin.View2")中的参数为“视图2”在plugin.xml中设置的id标识。由此可见,plugin.xml文件在插件中的地位是极其重要的。IWorkbenchPage除了findView方法之外,还用findEditor方法来得到编辑器对象。
像“cn.com.chengang.myplugin.View2”这种标识符在系统开发中会经常用到,最好建一个类来集中放置这些字符串常量,然后系统中用的时候只用其常量名即可,否则把标识符的字串分散在代码中,以后改起来会非常麻烦。常量类的示意代码如下:
public final class StringConstants {
public final static String VIEW1 = "cn.com.chengang.myplugin.View1";
public final static String VIEW2 = View2.class.getName();
}
要用的时候则这样写:
IViewPart view2 = wbp.findView(StringConstants.VIEW2);
20.3.2 监听式
Eclipse环境的3个视图“包资源管理器、大纲、属性”,当双击包资源管理器中的结点时,大纲和属性视图也跟着改变。当然用前面讲的主动式来实现这个效果。不过超过一个视图,用主动式就比较麻烦了。可以随着包资源管理器结点而需要改变的可能不止是大纲、属性视图,这时主动式就力所不及了。对于这种情况,则可以使用监听式。
1.基本实例
例如,View1、View2和View3视图,其中View2、View3需要监听View1中表格的选择事件。可以这样实现:
(1)在View1类的createPartControl方法中加上如下一句:
getSite().setSelectionProvider(tableViewer); //假设视图中有一个表格对象tableViewer
setSelectionProvider方法的参数类型是ISelectionProvider(provider翻译为提供者),而TableViewer类正好实现了这一个接口(TreeViewer也一样)。加此一句之后,如果再选择表格行时,底层事件机制将会通知所有监听者。
(2)接着需要在View2、View3中各添加一个监听器到底层,一般也是写在
createPartControl方法中,代码如下所示:
getSite().getPage().addSelectionListener(new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
String partId=part.getSite().getId();
if (partId.equals("cn.com.chengang.myplugin.View1")){
System.out.println(part.getTitle()); //part就是View1对象
System.out.println(selection); //selection就是被选择的表格行所代表的记录对象
}
}
});
这样,就在View2、View3中截获了View1的选择事件。由于底层的选择提供者可能不仅仅是View1,所以才需要在View2、View3的监听代码中根据View1对plugin.xml中的id标识做一下判断。当然,也可以将这个判断交由底层来负责,如下所示:
getSite().getPage().addSelectionListener("cn.com.chengang.myplugin.View1",new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
System.out.println(part.getTitle()); //part就是View1对象
System.out.println(selection); //selection里包含了被选择的表格行记录对象
}
});
2.进阶实例
如果View1中有两个表格怎么办?像下面这样是行不通的。
getSite().setSelectionProvider(tableViewer1);
getSite().setSelectionProvider(tableViewer2);
既然一个视图中只能设置一个选择提供者,那么可以换一种思路:创建一个自定义的选择提供者,然后由这个选择提供者收集tableViewer1、tableViewer2的选择事件集中传到底层。
自定义选择提供者就需要实现ISelectionProvider接口。查了一下该接口的层次结构发现有一个SelectionProviderAdapter适配器类,可惜它不是public类,无法继承它。那么就将SelectionProviderAdapter的代码复制到如下MySelectionProvider类中,并略做修改。
class MySelectionProvider implements ISelectionProvider{
List listeners = new ArrayList();
ISelection theSelection = StructuredSelection.EMPTY;
public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}
public ISelection getSelection() {
return theSelection;
}
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
listeners.remove(listener);
}
public void setSelection(ISelection selection) {
theSelection = selection;
final SelectionChangedEvent e = new SelectionChangedEvent(this, selection);
Object[] listenersArray = listeners.toArray();
for (int i = 0; i < listenersArray.length; i++) {
final ISelectionChangedListener l = (ISelectionChangedListener) listenersArray[i];
SafeRunner.run(new SafeRunnable() {
public void run() {
l.selectionChanged(e);
}
});
}
}
}
现在有了选择提供器,但是收集View1中两表格选择事件的功能还没有实现。可以发现TableViewer有一个addSelectionChangedListener方法,它能够监听表格的选择事件,但它接受的参数类型是ISelectionChangedListener。可以再单独创建一个ISelectionChangedListener接口的实现类,也可以让MySelectionProvider实现此接口,从而让MySelectionProvider既是底层的选择提供者,又是表格的选择事件的监听者。这里采用后一方案,让MySelectionProvider再实现ISelectionChangedListener接口,如下所示:
class MySelectionProvider implements ISelectionProvider,ISelectionChangedListener{
……原代码不变,省略
public void selectionChanged(SelectionChangedEvent event) {
setSelection(event.getSelection());
}
}
View2、View3中的代码不必修改,只需把View1类中的相应代码修改如下:
MySelectionProvider selectionProvider=new MySelectionProvider();
tableViewer1.addSelectionChangedListener(selectionProvider);
tableViewer2.addSelectionChangedListener(selectionProvider);
getSite().setSelectionProvider(selectionProvider);
这里只提到了视图,实际上任何WorkbenchPart的子类都可以使用这种机制,包括编辑器。另外,由于可以创建自定义选择提供者,所以可以不仅限于监听TreeViewer或TableViewer,也可以监听Combo、Text等组件的非选择事件,只需将要传送的信息包装成一个ISelection对象传给MySelectionProvider.setSelection方法即可。下面的代码就可以使得View1中的文本框组件的每次击键字符传播给各个视图的监听器。
final MySelectionProvider selectionProvider=new MySelectionProvider();
text.addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
String s = String.valueOf(e.character);
ISelection selection=new StructuredSelection(s);
selectionProvider.setSelection(selection);
}
public void keyReleased(KeyEvent e) {}
});
%注意:滥用底层事件广播机制可能会对性能有影响,但这需要用户在实际开发中做出测试和评估,以确定方案是否真的对性能造成了影响,而不是想当然。






