Eclipse 插件开发 —— 深入理解查找(Search)功能及其扩展点

引言

查找功能是计算机语言开发环境 / 平台的一个非常重要的特性。Eclipse 也不例外,它提供了丰富的查找功能(用户可以输入正则表达式或任意字符串,指定查找范围和匹配选项等等),并且提供了简单易用的接口方便开发人员扩展。Eclipse 的查找功能是基于 MVC 设计模式架构的,因此如果读者先前对 MVC 模式了解的话,有助于读者理解 Eclipse 的查找框架。在 Eclipse 的开发平台中,通过快捷键 CTRL+H 或者 Search 菜单可以激活其查找功能,如图 1 为 Eclipse3.4 插件开发版本按下快捷键时弹出的查找对话框(Search Dialog)。


图 1. Eclipse3.4 版本的查找对话框
图 1. Eclipse3.4 版本的查找对话框 

如图 2 为 IBM RSA7.5.2 版本按下快捷键时弹出的查找对话框,提供了多大八种查找功能。


图 2. IBM RSA7.5.2 版本的查找对话框
图 2. IBM RSA7.5.2 版本的查找对话框 

总体而言,Eclipse 中的查找功能由三部分组成:查找对话框、查找页面和查找结果集视图;下面分别就这三部分进行具体描述。

查找对话框是 Eclipse 查找功能的入口,Eclipse 中所有的查找功能都可以在该对话框中找到,如图 1 和图 2 显示了 Eclipse 和 IBM RSA 中的查找对话框。查找对话框中包含了当前 Eclipse 开发平台提供的所有查找页面,如果用户页面觉得页面太多,可以通过“Customize …”按钮定制,只显示使用频率较高的查找页面,隐藏其它页面。虽然我们不能对这些页面排序,然而,Eclipse 提供了相应的扩展点(Extension point)让开发人员定制其查找页面的位置(通过 tabPosition 属性可以指定,下一节会进行详细介绍)。

查找页面是查找功能的入口,提供了查找模式和条件等内容的输入界面 GUI。在 Eclipse 中,为了保持查找功能的一致性,当我们的插件需要提供查找功能,通常会为其创建一个查找页面,并放在查找对话框里面。每一个查找功能对应有一个查找页面,用于接收用户进行查找的内容,范围等输入条件。如图 3 为 Clear Case 插件的查找页面。


图 3. Eclipse 中 ClearCase 的查找页面
图 3. Eclipse 中 ClearCase 的查找页面 

作为一个完整的查找框架,还需要提供一个显示查找到结果集的界面。在 Eclipse 中,这个界面叫做查找结果集视图,它提供了丰富的工具栏和菜单,诸如上 / 下一个匹配项、重新查找、展开和收缩结果集等操作,并且可以根据查找结果的类型用相应的编辑器打开。Eclipse 中,所有的查找功能共用一个视图,该视图提供两种显示方式:表格(TableViewer)和树状结构(TreeViewer)。查找结果视图的标题为“Search”,如图 4 所示为作者使用 Eclipse 自带的文件查找搜索“*”的结果集视图。


图 4. Eclipse 中显示查找结果的视图
图 4. Eclipse 中显示查找结果的视图 

 

Eclipse 查找功能的扩展点

由上一节我们可知,Eclipse 的查找功能主要是由三部分组成:查找对话框、查找页面和查找结果集视图,至此,我们对 Eclipse 的查找功能有了感观的认识,本小节将从 Eclipse 提供的扩展点,函数,接口和类等方面详细说明 Eclipse 的查找功能。

查找对话框 / 查找页面的扩展点

如果我们想要插入一个查找页面到查找对话框中,那么需要添加扩展点 org.eclipse.search. searchPages。

该扩展点允许其他插件为特定的查找功能注册属于自己的查找页面。下面说明需要注意的扩展点的一些属性:

  • id – 查找页面的唯一标志。
  • label – 显示在查找对话框中页面的标题。
  • class –创建查找页面显示的 control 的类,该类通常继承抽象类 org.eclipse. jface.dialogs. DialogPage,并且实现接口 org.eclipse.search.ui. ISearchPage。
  • sizeHint – 暗示该查找页面激活时的大小,其输入格式为"width, height", 如"50, 60"。
  • tabPosition – 整形数字,表示该查找页面在查找对话框中显示的位置,查找对话框中包含一个 TabFolder,TabFolder 包含了若干个页面,如果该元素没有指定,那么 Eclipse 将按照这些页面标题的字母顺序进行排列。数字越小,其对应的页面越靠前。
  • enabled – 如果该属性没有指定或者为 false,那么当用户通过 CTRL+H,或者 search 菜单激活查找对话框时,该页面不会自动显示,用户可以通过“Customize” 按钮手动显示该页面。
  • canSearchEnclosingProjects 和 showScopeSection – 如果这两个属性都设置为 true,那么 Eclipse 会自动添加一个 Scope 到你的查找页面的最下面,如图 5 所示为 File 查找的 Scope 域,不需要用户单独创建该区域。


图 5. 文件查找页面的 Scope 区域
图 5. 文件查找页面的 Scope 区域 

为了更好地说明如何使用该扩展点,举一个例子说明,如下为 org.eclipse.search 插件中的文件查找页面实现的扩展点代码,位于 plugin.xml 文件中。

 <extension point="org.eclipse.search.searchPages"> 
    <page 
        id="org.eclipse.search.internal.ui.text.TextSearchPage"
    label="%fileSearch"
    icon="$nl$/icons/full/elcl16/tsearch_obj.gif"
    sizeHint="250,160"
    tabPosition="1"
    extensions="*:1"
    showScopeSection="true"        
    canSearchEnclosingProjects="true"
    class="org.eclipse.search.internal.ui.text.TextSearchPage"> 
 </page> 
 </extension> 

 

查找结果视图(Search Result View)的扩展点

对 org.eclipse.search.searchPages 扩展后,当我们按下查找(Search)按钮后,Eclipse 的查找框架会激活查找结果集视图,那么如何让我们自己设计的界面显示在查找结果集视图中呢?答案很明显,通过添加 org.eclipse.search.seacrhResultViewPages 扩展点,填充相应的扩展点属性,便可以激活我们自己设计的界面,并用该界面显示查找到的匹配结果集。下面讲解扩展点 seacrhResultViewPages 中比较重要的一些属性。

  • searchResultClass – 用于表示查找结果集的类,需要实现接口 ISearchResult,由于实现该接口需要实现较多的方法,因此 Eclipse 提供了一个抽象类,用于表示文本查找的结果集,该抽象类(类名为 AbstractTextSearchResult)实现了 ISearchResult 的大部分接口,如果我们实现的查找功能是文本查找,那么就可以继承该类,同时实现少量的方法即可,该类需要和类 AbstractTextSearchViewPage 同时使用
  • class - 用于显示查找结果的页面类,需要实现接口 org.eclipse.search.ui. ISearchResultPage,由于实现该接口需要实现较多的方法,因此 Eclipse 提供一个抽象类(类名为 AbstractTextSearchViewPage)实现了该接口,该类主要用于显示文本查找的结果集。如果我们实现的查找功能是文本查找,那么只需继承该类并实现少量的方法即可。AbstractTextSearchViewPage 和 AbstractTextSearchResult 通常一起使用,当然还需要用到 Match 类和 MatchEvent 等类,下小节将会详细说明。

为了更好地说明如何使用该扩展点,举一个例子说明,如下为 org.eclipse.search 插件中的文件查找结果集视图实现的扩展点代码,位于 plugin.xml 文件中。

 <extension 
    id="FileSearchPage"
    point="org.eclipse.search.searchResultViewPages"> 
    <viewPage 
    id="org.eclipse.search.text.FileSearchResultPage"
 searchResultClass="org.eclipse.search.internal.ui.text.FileSearchResult"
    helpContextId="org.eclipse.search.file_search_result_page"
    class="org.eclipse.search.internal.ui.text.FileSearchPage"> 
 </viewPage> 
 </extension> 

 

Eclipse 主菜单的查找(Search)菜单中加入你的菜单项

对一些重要的查找功能,我们希望提供多种途径激活这些功能。如添加一个菜单项到查找(Search)主菜单中,如图 6 所示为文件查找的菜单项。


图 6. Eclipse 自带文件查找的菜单
图 6. Eclipse 自带文件查找的菜单 

那么如何添加我们自己的菜单项到 Eclipse 的查找主菜单中呢?答案是:添加 org.eclipse.ui. actionSets 扩展点,然后创建一个 action, 并且 action 的 menuBarPath 设置为 org.eclipse.search. menu/dialogGroup。

为了更好地说明如何使用该扩展点,举一个例子说明,如下为创建一个”Am Search …”菜单项到 Search 主菜单中实现的扩展点代码,位于 plugin.xml 文件中。

 <extension 
   point="org.eclipse.ui.actionSets"> 
   <actionSet 
         id="org.eclipse.am.ui.workbenchActionSet"
         label="%actionSet.am.label"
         visible="true"> 
         <action 
            class="org.eclipse.am.ui.handlers.ESearchHandler"
            definitionId=" org.eclipse.am.ui.commands.ESearch"
            icon="icons/Search.gif"
            id=" org.eclipse.am.ui.commands.BGSearch"
            label="AM Search …"
            menubarPath="org.eclipse.search.menu/dialogGroup"
            style="push"> 
         </action> 
      </actionSet> 
 </extension> 

 

相关的函数、接口和类

对查找结果集排序

通常我们以表格(TableViewer)和树状结构(TreeViewer)显示匹配的结果集,因此,自然涉及到对结果集排序的问题。在 Eclipse3.0 版本之前,如果需要对查找结果集视图中的 TableViewer 或 TreeViewer 进行排序,则需要使用扩展点 org.eclipse.search.searchResultSorters。但是,3.0 以后的版本不需要添加该扩展点,可以直接写代码对 TableViewer 或 TreeViewer 排序,只需继承 org.eclipse.jface.viewers.ViewerSorter。下面给出了一个简单的排序类代码。如果想让下面代码工作,则需要设置该类为 TableViewer 的 Sorter,通过调用函数 setSorter 方法实现。

 public class ESearchResultSorter  extends ViewerSorter { 
        
        private int columnIndex = -1        
        private int dir = SWT.DOWN; 
       
        public BGSearchResultSorter(int columnIndex, int dir) { 
            super(); 
            this. columnIndex = columnIndex; 
            this.dir = dir; 
        } 
        public int compare(Viewer viewer, Object e1, Object e2) { 
            int returnValue = 0; 
             If(0 == columnIndex){ 
            returnValue = StringUtil.getCollator().compare(e1,e2); 
          } 
            if (this.dir == SWT.DOWN) { 
                returnValue = returnValue * -1; 
            } 
            return returnValue; 
        }        
 } 

 

Match 和 SearchResultEvent 类

Eclipse 针对文本查找,除了提供前面讲到的 AbstractTextSearchViewPage 和 AbstractTextSearchResult 类外,还提供了配套的类 Match,MatchEvent 和 RemoveAllEvent(均继承类 SearchResultEvent),从而实现文本查找功能。如果我们也需要开发基于文本查找的功能,那么完全可以继承或使用这些类,否则,为了与 Eclipse 的查找框架保持一致和加强我们插件的可扩展性,建议大家也创建类似的类,并实现相应的功能。下面分别就这些类进行说明。

1. org.eclipse.search.ui.text.Match 类

该类用于表示查找过程中匹配的对象,是 Eclipse 为文本查找定义的 Match 类,该类包含匹配的元素(文件或资源等等),查找字符串在该元素中的起始偏移量(offset)和匹配长度,如果我们的查找功能是基于文本的,那么可以继承该类,否则,我们可以自己定义 Match 类(不用继承该类)。

2. org.eclipse.search.ui.SearchResultEvent 类

该类保存了提供给事件(Event)接收者需要的对象,例如,如果该事件为增加一个 Match 类对象,那么通过这个事件可以获取该对象。基于文本查找的两个时间 MatchEvent 和 RemoveAllEvent 均继承该类,其中 MatchEvent 类用于增加或删除 Match 对象,RemoveAllEvent 为删除所有 Match 对象。

因此,我们在开发查找功能时需要继承该类 SearchResultEvent,提供我们的查找功能与该事件相关的对象,由 org.eclipse.search.ui.ISearchResult 激活该事件。一般而言,继承类会提供事件的类型(如增加,删除匹配项等),匹配的结果集(如包含 Match 的集合(Collection)),可以参考 MatchEvent 和 RemoveAllEvent 类。

3. org.eclipse.search.ui.ISearchResult 接口

该接口用于表示查找结果集。前面讲到的 org.eclipse.search.searchPages 扩展点的属性 searchResultClass 对应的类需要实现该接口。Eclipse 提供的表示文本查找结果集的抽象类 org.eclipse.search.ui.text.AbstractTextSearchResult 实现了该接口。如果我们实现基于文本查找的功能,那么可以继承该类,实现少量的方法即可。一般而言,在实现该接口的继承类中会描述匹配结果集的结构,例如,包括了 Match 集合和 org.eclipse.search.ui.ISearchResultListener(下面将会讲到)集合等。

4. org.eclipse.search.ui.ISearchResultListener 接口

该接口表示查找结果集发生变化的事件接口。当结果集发生变化时,通知事件监听者(这里为查找结果集视图)作出相应的行为。该接口中提供了一个方法 searchResultChanged,其参数为 SearchResultEvent。

就实现而言,我们可以让查找结果集视图实现该接口,并调用模型(Model,我们这里表现为 ISearchResult)中的方法注册 / 注销该事件(继承方式),另外,我们也可以在查找结果集视图的构造函数中定义匿名类,实现该接口中的方法,同时调用模型中的方法注册 / 注销该匿名类事件(组合方式)。从大部分的实现方法来看,我们会使用后者(组合方式),因为该接口中只有一个方法,并且使用匿名类也更加灵活。Eclipse 针对文本查找的结果集视图抽象类 org.eclipse.search.ui.text.AbstractTextSearchViewPage 也是采用匿名类的方式。

5. org.eclipse.search.ui.NewSearchUI 类

该类提供了访问 Eclipse 查找 GUI 的入口方法,它采用 Facade 设计模式。下面就该类中的一些重要函数进行说明。

调用下面这个方法表示在当前的 Page 里激活查找结果集视图(search result view)。

     activateSearchResultView() 

 

调用下面这个方法表示发送‘ cancel ’命令到后台运行的 query。

	 cancelQuery(ISearchQuery query) 

 

调用下面这个方法表示获取当前的查找结果集视图。

	 getSearchResultView() 

 

调用下面这个方法表示打开查找对话框,并激活由 pageId 指定的查找页。

	 openSearchDialog(IWorkbenchWindow window, String pageId) 

 

调用下面这些方法表示运行指定的 query,可以在前台或者后台运行,此时 Eclipse 会启动一个 job 运行。

 runQueryInBackground(ISearchQuery query) 
 runQueryInBackground(ISearchQuery query, ISearchResultViewPart view) 
 runQueryInForeground(IRunnableContext context, ISearchQuery query) 
 runQueryInForeground(IRunnableContext context, ISearchQuery query, 
                                        ISearchResultViewPart view) 

 

 

编程实践

通过前两节的讲述,相信大家对 Eclipse 的查找框架已经很清楚了,下面将给出一个例子说明如何使用这些扩展点,如何实现接口和继承类,如何让我们的查找功能在 Eclipse 的查找框架下工作。

首先,如图 7 给出了 Eclipse 查找框架的流程,本文将按照这个流程图中的步骤及其各个步骤涉及到的方法,接口和类,给出它们的代码框架,读者想要让其运行,必须实现相应的方法,接口和类。


图 7. Eclipse 查找框架的流程
图 7. Eclipse 查找框架的流程 

开始阶段(弹出查找对话框)

当用户按下 Ctrl + H 键,或者通过 Eclipse 的 Search 菜单选择相应的查找项(如果我们定义了自己的 Action 在 Search 菜单中),Eclipse 将会弹出查找对话框。

如果只需要 Crtl+H 激活查找对话框,那么添加 searchPages 扩展点,并填写相应的属性,而不需要其他额外的代码,在查找对话框中就会有我们的查找页(还记得前面讲的 searchPages 扩展点的 enable 属性吗,如果该属性设为 true,那么扩展的查找页将会出现在对话框中,否则将被隐藏,需要通过“Customize …”按钮激活)。

如果需要在 search 菜单中定义自己的菜单,那么首先添加 actionSets 扩展点,如下所示。

 <extension 
      point="org.eclipse.ui.actionSets"> 
      <actionSet 
         id="com.ibm.bg.ui.workbenchActionSet"
         label="Example"
         visible="true"> 
         <action 
            class="com.ibm.bg.ui.handlers.ESearchHandler"
            definitionId="com.ibm.bg.ui.commands.ESearch"
            icon="icons/lookup_criteria.gif"
            id="com.ibm.bg.ui.commands.ESearch"
            label="E Search"
            menubarPath="org.eclipse.search.menu/dialogGroup"
            style="push"> 
        </action> 
      </actionSet> 
 </extension> 

 

然后再事件处理中通过 NewSearchUI 提供的 openSearchDialog 函数打开对话框,此时需要提供查找页的 ID,这里我们定义为 ESearchPage,后面将会讲到。

 public class ESearchHandler implements IWorkbenchWindowActionDelegate { 
    private IWorkbenchWindow fWindow; 
    public void init(IWorkbenchWindow window) { 
        fWindow = window; 
    } 
    public void run(IAction action) { 
        if (fWindow == null || fWindow.getActivePage() == null) { 
            Activator.beep(); 
            return; 
        } 
        NewSearchUI.openSearchDialog(fWindow, ESearchPage.ID); 
    } 
 } 

 

用户输入(查找页面)

所有查找功能的输入都是在查找页面进行的,因此,我们需要使用 searchPages 扩展点,提供自己的查找页面,从而在该页面内定制我们的 GUI(如何摆放 Widget,如何布局等等)。以下是扩展点的实现。

 <extension 
      point="org.eclipse.search.searchPages"> 
      <page 
         class="org.eclipse.book.example.pages.ESearchPage"
         enabled="true"
         id="org.eclipse.book.example.pages.ESearchPage"
         label="Search Example"> 
 </page> 
 </extension> 

 

下面分析 ESearchPage 的代码,该类继承 DialogPage,并且实现 ISearchPage 接口。该类中,我们需要重载 createControl 方法,用于创建 Widgets,布局我们的查找页面。通常,我们需要把当前用户的输入和选择保存到 DialogSetting 中,如果查找页面需要输入文本,那么我们可以定制历史输入的个数,该值通常为 20(Eclipse 文件查找)。需要注意:在 createControl 方法中,我们必须调用方法 setControl 来设置查找页面的上层控件,否则的话,查找页面会创建失败(后面会给出失败的原因)。下面给出了该类的一些关键代码为参考之用。

 public class ESearchPage extends DialogPage implements ISearchPage { 

    private ISearchPageContainer fContainer; 
    // 定义页面需要的 control 

    public ESearchPage() { 
            } 

    @Override 
    public void createControl(Composite parent) { 
        // 创建显示在查找页面的 control 
        ……
        setControl(parent); 
        ……
    } 
    public boolean performAction() { 
        NewSearchUI.activateSearchResultView(); 
        NewSearchUI.runQueryInBackground(getSearchQuery()); 
        return true; 
    } 
    public ISearchQuery getSearchQuery() { 
        return new ESearchQuery(this.fPattern.getText().trim()); 
    } 
    protected ISearchPageContainer getContainer() { 
        return this.fContainer; 
    } 
    public void setContainer(ISearchPageContainer container) { 
        this.fContainer = container; 
    } 
 } 

 

前面讲到,如果不在 createControl 方法中调用 Setting 方法,那么查找页面的 widgets 将会创建失败,为什么呢?如果你单步跟踪调试,会发现类 org.eclipse.search.internal.ui.SearchDialog 中包含调用我们创建的查找页面代码,如下所示,从代码中可以看出,如果没有调用 setControl 方法,那么 page.getControl() 将返回 Null 值,故创建失败,Eclipse 将提示“The creation of the page '…' failed.”错误信息。

 private Control createPageControl(Composite parent, 
                           final SearchPageDescriptor descriptor) { 
    ……
    ISearchPage page= descriptor.getPage(); 
    if (page == null || page.getControl() == null) { 
        Composite container= new Composite(parent, SWT.NONE); 
        Label label= new Label(container, SWT.WRAP); 
        label.setText(Messages.format(SearchMessages. 
 SearchDialog_error_pageCreationFailed, descriptor.getLabel())); 
            container.setLayout(new GridLayout()); 
            label.setLayoutData(new GridData()); 
        return container; 
    } 
 } 

 

查找入口(启动 Job)

当我们点击查找对话框中“Search”按钮,这时候会调用 ESearchPage 类中的 performAction() 方法。该方法将启动 Job 执行 Query(实现了 ISearchQuery 的类),我们可以选择在前台还是后台运行(具体方法,可以参考上一节中 NewSearchUI 提供的静态方法)。一般而言,performAction 方法首先调用函数 activateSearchResultView() 激活查找结果视图,然后调用函数 runQueryInBackground() 运行指定的 Query。

上面讲到,在运行 Query 之前,通常会调用方法 NewSearchUI.activateSearchResultView() 激活查找结果视图(需要使用 searchResultPages 扩展点),此时仅仅是当前查找功能的结果集界面显示。查找结果视图继承 Page 类,并实现 ISearchResultPage 接口,下面分析该类中的一些关键代码。通常我们会在构造函数中定义 ISearchResultListener,并提供相应的监听方法。在 setInput 方法中,需要根据当前显示在查找结果视图中的匹配结果集合和新的匹配结果集合状态和值,注册或注销该事件。所有的匹配结果集合均通过调用查找结果集视图中 TableViewer 的内容提供者(Content Provider)逐个插入到 TableViewer 中。getLabel() 用于显示在 TableViewer/TreeViewer 上面的一个 label 的内容,通常用于显示查找到多少个匹配项。下面给出了部分关键代码,读者可以参考 AbstractTextSearchViewPage 类中的一些实现。

 public class ESearchResultPage extends Page implements ISearchResultPage{ 
    private TableViewer fTableViewer; 
    protected ESearchResult fInput; 
    protected ISearchResultViewPart fViewPart; 
    protected ISearchResultListener fListener; 
    protected String fId; 
    public ESearchResultPage() { 
        this.fListener = new ISearchResultListener() { 
            public void searchResultChanged(SearchResultEvent e) { 
                ESearchResultPage.this.handleSearchResultChanged(e); 
            } 
        }; 
    } 
    public void createControl(Composite parent) { 
        } 
    public String getLabel() { 
        ESearchResult result = getInput(); 
        if (result == null) 
            return ""; 
        return result.getLabel(); 
    } 
    public void setInput(ISearchResult search, Object uiState) { 
        ISearchResult oldSearch = (ISearchResult) this.fTableViewer.getInput(); 
        this.fTableViewer.setInput(null); 
        if (oldSearch != null) { 
            oldSearch.removeListener(this.fListener); 
        } 
        this.fInput = ((ESearchResult) search);        
        if ( fInput != null && fInput.getElements().size() > 0) { 
            search.addListener(this.fListener); 
            this.fTableViewer.setInput(search); 
            if (uiState instanceof ISelection) 
                this.fTableViewer.setSelection((ISelection) uiState, true); 
        } 

    } 

 

查找(执行 Query)

当调用 runQueryInBackground 或 runQueryInForeground 方法之后,Eclipse 查找框架会启动一个 Job 运行我们定义的 Query,该类是 Eclipse 查找框架中最主要的一个类之一,它定义了如何进行查找,同时把查找匹配项添加到 ISearchResult,并且以友好的用户体验方式即 progressMonitor 显示目前查找的进度。该类提供了很多方法,例如,是否可以重新运行,是否可以在后台运行及显示进度的对话框的标题等等,下面给出类 ESearchQuery 的部分关键代码的实现。

 public class ESearchQuery implements ISearchQuery { 
    ESearchResult fSearchResult; 
    String fPattern; 

    public ESearchQuery(String pattern) { 
        this.fPattern = pattern; 
        this.fSearchResult = ESearchResult.createInstance(this); 
    } 
    public boolean canRerun() { 
        return true; 
    } 
    public boolean canRunInBackground() { 
        return true; 
    } 

    public String getLabel() { 
            return "Example Search"; 
    } 
    public ISearchResult getSearchResult() { 
        return fSearchResult; 
    } 
    public IStatus run(IProgressMonitor monitor) 
            throws OperationCanceledException { 
        try{ 
            // 表明查找开始
            monitor.beginTask(taskname, totalCount); 
        // 在指定的范围逐个查找结果集,如果找到,添加到 ISearchResult 
        // 其流程大致如下
        For( … ){ 
            找到匹配项
            构造一个 Match 
            调用 ISearchResult 的函数添加该 Match 
            Monitor.work(1) 
        } 
        } catch (RuntimeException e) { 

        } finally { 
            if (monitor != null) 
                monitor.done(); 
        } 
    } 
 } 

 

从上面的描述可知,在类 ESearchQuery 会调用 ESearchResult 类中的方法添加查找的匹配项,接下来将讲解查找结果类 ESearchResult。

EsearchResult 类实现 ISearchResult 接口,用于保存查找到的匹配项。该类除了要实现最重要的两个方法,添加和删除 ISearchResultListener 方法外,同时需要定义添加匹配的结果集,删除结果集等方法。下面给出该类中的一些关键代码,其中添加匹配的结果集的方法 addMatch,添加传入的匹配项到匹配结果集合中,同时通知 ISearchResultListener 事件的监听者。

 public class ESearchResult implements ISearchResult { 
    protected List<EMatch> elements; 
     ……
    public void addMatch(Match match) { 
        boolean hasAdded= false; 
        synchronized (elements) { 
            hasAdded= doAddMatch(match); 
        } 
        if (hasAdded) 
            fireSearchResultEvent(ESearchResultEvent.ADDED, match) 
    } 
    protected void fireSearchResultEvent(ESearchEventType eventType, 
            EMatch match) { 
        ESearchResultEvent event = new ESearchResultEvent(this, eventType); 
        event.setMatche(match); 

        for (Iterator<ISearchResultListener> e = this.searchResultsListeners 
                .iterator(); e.hasNext();) { 
            ISearchResultListener listener = (ISearchResultListener) e.next(); 
            listener.searchResultChanged(event); 
        } 
    } 

 

上面讲到 ESearchResult 类包含了匹配项的集合,一般我们会定义一个用于保存匹配对象的类,如 Eclipse 中为文本查找的匹配项提供了 Match 类,该类定义了匹配项的偏移量,长度,匹配的对象等等,如果我们的匹配项具有这些特征,那么可以继承该类,否则新建一个 Match 类即可。下面是一个简单的匹配类的实现 EMatch 类。

 public class EMatch { 
    EDataObject fMatchElement; 
    
    public String getName(){ 
        return fMatchElement.getName(); 
    } 

    public EMatch(EDataObject matchElement) { 
        fMatchElement = matchElement; 
    } 

 } 

 

同时,在查找到匹配项,需要通知事件的监听者当前事件的类型(通常有三种:增加了匹配项,删除匹配项和清空匹配项),Eclipse 查找框架提供了 SearchResultEvent 基类,文本查找定义了两个类:MatchEvent(包含增加和删除匹配项类型事件),RemoveAllEvent(清空匹配项事件)。通常在我们开始查找之前,发送清空匹配项类型事件,让查找结果集视图清空上一次的匹配项,然后再查找的过程,如果查找到匹配项,则发送增加匹配项事件,使查找结果集视图把该匹配项添加到界面上(TableViewer 或 TreeViewer)。这里不给出查找结果集事件的关键代码,因为 MatchEvent 和 RemoveAllEvent 的源码已经很清晰,读者可以参考这两个类的实现。

显示查找结果集

前面在讲激活查找结果集视图的时候,已经说明在 Eclipse 的查找框架内,并不是通过 TableViewer 的 setInput 方法把匹配的结果集显示在 Search 视图中,而是通过 TableViewer 的 add 方法把匹配项添加到查找结果集视图中。具体而言,Eclipse 查找框架会启动一个 UIJob 定时地把查找到的匹配项添加到 Search 视图中,这样可以增强用户体验。以 AbstractTextSearchViewPage 中的 UpdateUIJob 为例,其间隔时间为 500 毫秒。通过调用 runBatchedUpdates 方法把当前查找到的匹配项添加到 Search 视图中,以下是其中的一些关键代码。

 private class UpdateUIJob extends UIJob {        
        public UpdateUIJob() { 
            super("updating");  //$NON-NLS-1$ 
            setSystem(true); 
        } 
        
        public IStatus runInUIThread(IProgressMonitor monitor) { 
            Control control= getControl(); 
            if (control == null || control.isDisposed()) { 
                // disposed the control while the UI was posted. 
                return Status.OK_STATUS; 
            } 
            runBatchedClear(); 
            runBatchedUpdates(); 
            if (hasMoreUpdates() || isQueryRunning()) { 
                schedule(500); 
            } else { 
                fIsUIUpdateScheduled= false; 
                updateBusyLabel(); 
                if (fScheduleEnsureSelection) { 
                    fScheduleEnsureSelection= false; 
                    AbstractSearchResult result = getInput(); 
                    if (result != null && fViewer.getSelection().isEmpty()) { 
                        navigateNext(true); 
                    } 
                } 
            } 
            fViewPart.updateLabel(); 
            return Status.OK_STATUS; 
        } 
            
    } 

 

下面我们分析一下 runBatchedUpdates 中的代码,该函数会调用 elementsChanged 函数触发 TableViewer 调用 add 方法,把相应的匹配项添加到 TableViewer 中,然后清除从需要更新的匹配项集合中删除,如下为其关键代码。

 private synchronized void runBatchedUpdates() { 
    elementsChanged(fBatchedUpdates.toArray()); 
    fBatchedUpdates.clear(); 
    
    updateBusyLabel(); 
 } 

 

前面讲到,runBatchedUpdates 方法会将当前查找到的匹配项添加到 Search 视图中,那么如何获取当前查找到的匹配项呢?这就需要借助 ISearchResultListener,即当前查找到的匹配项通过我们定义的事件监听函数获取,如下为定义查找结果集发生变化的类及其监听函数 handleSearchResultChanged 中的关键代码。我们注意到,MatchEvent 中包含事件的类型和当前查找到的匹配项,如果当前事件为 REMOVED_ALL 类型,那么从查找结果视图中清除所有的匹配项。

 fListener = new ISearchResultListener() { 
    public void searchResultChanged(SearchResultEvent e) { 
        handleSearchResultChanged(e); 
    } 
 }; 

 protected void handleSearchResultChanged(final SearchResultEvent e) { 
        if (e instanceof MatchEvent){ 
            MatchEvent event = (MatchEvent) e; 
            if(event.getKind() == MatchEvent.REMOVED_ALL){ 
                postClear();                
            } 
            else{ 
                postUpdate(((MatchEvent) e).getMatches()); 
            } 
        }        
 } 

 

由于增加 / 删除匹配项设计 GUI 操作,因此我们需要启动 UIJob 运行,因此,runBatchedUpdates 函数调用 postUpdate 函数启动 Job,把匹配项显示到查找结果视图上。如下为 postUpdate 的一些关键代码。

 private synchronized void postUpdate(Match[] matches) { 
    evaluateChangedElements(matches, fBatchedUpdates); 
    scheduleUIUpdate(); 
 } 

 

 

总结

至此为止,已经把 Eclipse 查找框架的流程及涉及到的类进行了详细的说明,相信读者已经有清晰的认识了,但是,由于 Eclipse 中的所有查找功能都共用一个视图,导致这些查找功能不能同时使用,而且,有时候我们可能需要定义自己的查找结果集视图,那么 Eclipse 查找框架中的有些类就不能使用了,如 ISearchQuery,此时,我们不能再给其查找框架了,但是,这个框架的思想我们可以借鉴,读者有兴趣的话,可以开发类似于 Eclipse 的查找框架,但是使用的是我们自己定义的查找结果集视图。

下面给出如何使用自己的查找结果集视图显示匹配项的关键代码,其大致思想是新建一个 Job,在 Job 运行时进行查找,并把查找的匹配项保存起来,待 Job 运行结束后,激活我们自定义的查找结果集视图,最后把结果集填充到该视图中。

提示:Eclipse 查找功能是一个非常典型的 MVC 模式,设计我们自己的查找框架(Search Framework)时,完全可以参考该模式,简洁方便。

 public class SearchJob extends Job { 
    
    protected String fJobName; 
    protected String fTaskName; 

    protected IStatus run(IProgressMonitor monitor) { 
        // 查找代码,保存查找到的匹配项
    } 


    protected void performJobDone() { 
        // 启动 UI 线程,激活查找结果视图,并填充匹配项到视图中
    } 

    public SearchJob (String name) { 
        super(name); 
        fJobName = name; 

        this.addJobChangeListener(new JobChangeAdapter() { 
            @Override 
            public void done(IJobChangeEvent event) { 
                if (event.getResult().getSeverity() == IStatus.OK) { 
                    performJobDone(); 

                } 
            } 
        }); 
    } 

 } 

posted on 2013-07-06 04:40  Step-BY-Step  阅读(1489)  评论(0编辑  收藏  举报

导航