Netbeans 插件模块(Plugin Module)的开发

 By carol.zhang@sun.com,
3/6/07

Netbeans在近年的发展,可说是长足的进步。它不仅是功能强大的集成开发环境,更可以看作是一个开发框架和平台,基于这个平台,通过模块开发,扩展这个平台的功能,或者根据自己的需求,定
制个性化的IDE环境。

概述

Netbeans IDE由一个核心运行时环境(core runtime)和一组模块组成。这个core runtime为大多数桌面应用提供公共组件和服务,而“模块”,则是运行在这个core
runtime之上的java class,譬如,对于Java语言的支持,就是一个”plugin module”,所有Netbeans IDE的跟开发相关的功能都是以“模块”的方式提供的。



开发者可以根据Netbeans平台所开放的编程接口,开发自己的”plugin module”来实现特定的功能。一般来说,有这么两个目的:


  • 扩展Netbeans IDE。我们可以轻松的加入需要的特性,扩展IDE的功能。譬如,在工具栏上加入Google的搜索框;支持新的文件类型;在Netbeans
    IDE上支持新的web应用框架的开发;或者实现web 程序在特殊的应用服务器上的部署或调试,等等。
  • 在Netbeans核心平台之上,构造自己的rich client应用。譬如,构造一个有丰富的GUI界面的报表生成器。


从程序的角度看,一个module就是一个java的archive(.jar)文件,它包含多个java类,以及一个manifest文件,用来标志该jar文件是一个Plugin Module。当
平台的main class运行时,找到所有可用的module,建立一个内存登记表,并且运行这些module指定的在平台启动时的代码,module的其它代码则根据需要装载。



从发布的角度看,一个module是一个.nbm文件,使用者可以从利用Netbeans IDE中的“update center”功能,从网站下载.nbm文件并安装,或
者选择本地的.nbm文件进行安装。

 


创建Plugin Module的一般步骤


Netbeans从5.0版本开始,提供了向导(Wizard)和模板template,来帮助Netbeans module的开发。一般的开发过程是在基于Wizard生成的Template之上,添
加功能。下面以一个简单的Plugin Module开发过程(参考:http://platform.netbeans.org/tutorials/nbm-google.html)为例,来
介绍这个过程:


1. 创建Plugin Module Project



Step1


在创建project的界面上,如图所示,有三类:


  • Module Project:为一个standalone plugin module创建模板
  • Module Suite Project:为一组互相关联的plugin module和library wrapper module创建模板。它们将一起部署。
  • Library Wrapper Module Project:为一个外部的jar文件创建一个plugin module,可以包含在module suite project中。

这里选择Module Project就可。


Library Wrapper Module有什么用途?它帮助我们在Netbeans Plugin开发中和第三方jar文件建立依赖关系。举例:如果要申明本模块依赖test.jar,


  • 将test.jar包装成Library Wrapper Module,命名为TestModule
  • 在Project property的API versioning页中,设置这个module的版本号,以及public package(public
    package是指对于module之外的其它类可见的package)
  • 将这两个模块放到同一个Module Suite Project中
  • 在原module中的“add dependency”的界面中,加入它和TestModule的public package的依赖关系。

当然,如果模块依赖Netbeans本身提供的module,比如Data System API,只需最后一步即可,不用Library Wrapper Module。


这和我们在普通的java程序开发过程中,申明和.jar之间的依赖关系的方法有所不同,平时,我们只需将.jar文件放到classpath上即可。Netbeans
Module申明依赖关系的方式的好处在于保持很好的模块化,将模块之间的依赖关系最小化,清晰化,并且利用netbeans中的public package的功能,隐藏API的实现细节,利
于模块将来的升级和维护。


在接下来的界面中,给模块命名(MyFirstModule),选择模块所在的目录(d:\test\nbmodule),生成的java的包名(org.myorg.myfirstmodule)。点击“
finish”就完成了模板的建立。


2. 让我们看看模板中都包含些什么:


 



  • 源文件和单元测试的包。除了包含和大多数其它普通项目一样的源文件,它还包含一个重要文件:



    • 缺省在org.myorg.myfirstmodule的源文件包下,有一个“layer.xml”文件。它以xml的格式定义了系统的可扩展点,如新增加的 Menu,T
      oolbars,action属性等以及用户自定义的配置信息,这些信息将合并到整个netbeans系统的配置注册表中(以后会谈到,这个系统的配置注册表称为System FileSystem)。浏
      览layer.xml文件的内容,会发现它包含了“FileSystem”这样的tag。



     



  • Important Files:这个目录下包含了Plugin Module项目中重要的配置文件,其中比较重要的有:





  • XML Layer:它是上述的layer.xml文件的图形表达方式,可以通过图形界面对layer.xml进行浏览和修改



  • Module Manifest:位于项目根目录下的manifest.mf 文件指明该项目是一个plugin module,并
    包含layer.xml和bundle文件的位置



  • Project Metadata:包含项目的元数据,如项目类型,该模块和别的模块依赖关系等等。在Library Wrapper Module Project介绍中
    提到的依赖关系即在这里申明。




3. 在生成的项目上,就可以利用IDE提供的wizard来创建各种文件模板:



filetemplate


如图所示,Netbeans为plugin的开发提供了多种模板以供选择。除了常见的Java Package,Java Class和File/Folder等,有些模板是Plugin
Module开发专用的,它们提供了扩展系统的一些方式,运用模板能够迅速的搭起框架,或者获得示例的代码:如,Project Template可以以提供的项目结构和源码为基准,建立新的模板,加
入到主菜单的“File”-> “New Project”的可选项目模板中;Wizard可以帮助你建立一系列向导窗口;Window Component可以用来创建以
TopComponent为子类的窗口类,并且可将对应的窗口对象嵌入到IDE的指定位置;等等。值得注意的是,并不是所有的系统扩展和新功能的添加都能够,或者必须通过模板的方式来实现,如,希
望在Editor中添加代码自动完成的功能,则没有可用的模板,开发者应熟悉在layer.xml中可以添加的系统的扩展点,以及对应的code completion API。


这里,我们以生成一个新的Action为例。目的是要在系统的工具条上加上一个google搜索栏。当在搜索栏中输入文字,回车后,将调用GoogleAction。因此,在模板列表中选择“
Action…”,出现



NewAction


我们知道,凡菜单项或者工具条选项,都有两个状态,激活或者禁止。这里界面上有两个选项正是为这两种状态设置。“Always Enabled”,如名称所示,这个Action总是处于可调用的状态,因
而界面上的选项将总是处于激活的状态。而“Conditionally Enabled (use CookieAction)”则是根据当前所选择的界面对象的状态和条件,来决定是否激活Action。关
于界面上看到的DataObject,Node和Cookie等,我们将在后续的内容中介绍。这里简单起见,选择“Always Enabled”,下一步的界面“GUI Registration”:



GuiReg


容易推测出,这一步为Action在界面上注册一个调用入口,并且指定它的位置。它可以是一个菜单项,也可以工具条上的按钮,或者对应某个快捷键等。指定Toolbar和Position之后,在
接下来的步骤中指定类名GoogleAction等信息后,就完成了Action模板的建立。


让我们看看Netbeans自动为我们做了什么:



  • GoogleAction.java:新生成的CallableSystemAction的子类。注意,在代码中,i
    mport了org.openide.util等package。这意味着本 module依赖了其它的module。

  • Layer.xml:被修改。因为增加了新的action以及工具条选项。在之前提到的“FileSystem”下,新增加了一些“folder”和“file”。 这些“
    folder”和“file”正描述了这个action以及界面注册的情况等等。

  • Project metadata:被修改。因为增加了新的module dependency。

4. 基于自动生成的模板,运用Netbeans API编写代码,实现所需功能。


本例将创建JPanel 表单,并和GoogleAction关联。在项目名字上右键弹出的菜单中,选择“new”-> “JPanel Form”,在生成的Panel界面上,建立表单,编
写代码实现Google Search。最后重载GoogleAction类的getToolbarPresenter()方法,将JPanel实例作为返回值,即可。具体的步骤请参见
http://platform.netbeans.org/tutorials/nbm-google.html


5. 测试和安装


这两步也非常简单。选择右键菜单中的“Install/Reload in Target Platform”,即可以测试。选择“Create NBM”,可以创建可发布的nbm文件,
用户则可以用菜单中的“Tools”->“Update Center”来安装。如果想卸载,则可用“Tools”-> “Module Manager”功能。


这是非常简单的例子,但是在这个例中,我们介绍了plugin module的几种类型,创建和发布plugin module的一般步骤,module的基本组成,如何解决 netbeans
module dependency,怎么扩展系统的菜单,以及action API的基本用法。但是,在这个示例的介绍中,也留下了很多疑问,比如什么是 System FileSystem,D
ataObject,Node等等。在下文中,将比较深入的介绍基于netbeans平台,进行Plugin Module开发所涉及到的基本概念,以及其中API的使用。


 


基本概念 & API


我们知道,构建一个框架或者平台软件,必须考虑平台的通用性以及模块和平台之间的接口。当我们学习怎么基于netbeans的平台,做模块开发的时候,不仅 是单纯的开发过程,也
是一个了解netbeans的设计思想的过程。


文件系统 (FileSystem)


在netbeans 3.x的版本的IDE中,开发者经常要mount“filesystem”到一个虚拟的名字空间中,每个文件系统都有一个根(“root”),所
有在这个根下的文件都存于这个名字空间中。在netbeans 4.0以后,对于普通IDE使用者而言,在界面上,文件系统的概念已经不存在了,但是类似的概念仍然存在于IDE的基础架构中,开发plugin
module仍需涉及。


Netbeans中,文件系统(FileSystem)扮演着双重角色:它既可以代表磁盘上的物理文件系统,也可以代表IDE和module的配置数据构成的虚拟的文件系统。基于IDE
的配置数据的文件系统称为“System FileSystem”。在当前的FileSystem API中,可以从一个称为Repository的singleton对象里,用
Repository.getDefault().getDefaultFileSystem()方法获得“System FileSystem”,开发者利用System FileSystem,存
取系统的配置数据,以及特定配置目录下的磁盘文件。其它常见的文件系统有JarFileSystem和LocalFileSystem等。LocalFileSystem代表本地磁盘的文件系统。无论是物理的,还
是虚拟的文件系统,它们都共享FileSystem的抽象特性,以及基于其上的FileObject,DataObject,Node等概念。


从文件系统中,获得的是FileObject对象,它提供关于文件对象的基础信息(名字,父对象,是否存在等),和在对象上的操作(移动,删除等)。FileObject和
java.io.File的主要不同之一在于FileObject提供了change listener的机制。另外,FileObject并不一定是代表磁盘上的物理文件,这种情况在接下来介绍中,可
以看到实例。


物理的文件系统(如LocalFileSystem)是比较好理解的,可以参考相关API。这里,我们更需要关注关于System FileSystem的几个基本问题:

1. Netbeans plugin的配置数据是如何表示的?

2. 配置数据与System FileSystem的关系?

3. System FileSystem的根(“root”)在哪里?

4. System FileSystem的扩展点

5. 如何取得System FileSystem根下的物理文件?

6. 如何取得System FileSystem里的虚拟文件对象?


上文提到,每个module都是通过一个“layer.xml”文件来保存配置数据,文件中包含一些虚拟的“文件”和“目录”。这些layer.xml合并在一起就构成了System
FileSystem。module就是通过这种方式把配置数据加载到系统中。在module的jar文件中的manifest中,指明了这个layer.xml所处的位置,例如:


OpenIDE-Module-Layer: org/myorg/myfirstmodule/layer.xml


根据这个信息,可以在classes目录中找到这个文件。


一个简单的layer.xml文件就像:


<folder name=”Actions”>   

  <folder name=”Edit”>       

    <file
name=”org-myorg-myfirstmodule-GoogleAction.instance”/>   

  </folder>

</folder>



<folder name=”Toolbars”>

  <folder name=”Edit”>

    <attr
name=”org-openide-actions-FindAction.instance/org-myorg-myfirstmodule-GoogleAction.shadow”
boolvalue=”true”/>

    <file name=”org-myorg-myfirstmodule-GoogleAction.shadow”>

      <attr name=”originalFile”
stringvalue=”Actions/Build/org-myorg-myfirstmodule-GoogleAction.instance”/>

    </file>

  </folder>

</folder>



<folder name=”myFolder”> 

  <file name=”vFile”>

     <attr name=”attr1”
stringvalue=”attrValue”/>   

  </file>   

  <file name=”myFile.txt” url=”resource/realfile”/>

</folder>



其中有一些“folder”是系统预先定义好的,比如,例中的“Actions”和“Toolbars”,当module往IDE的toolbar上添加新的选项的时候,在 “Toolbars”以
及它的子目录下,必然出现对应与这个新选项的“file”条目和相关的属性。可以看到,这些预定义的folder正是这个系统的扩展点。


还有一些folder是用户自定义的,比如例中的“myFolder”,注册于System FileSystem中,可以动态保存用户自己的配置或者资源信息。这些信息可以以虚拟文件的形式,存
于layer.xml中:



FileObject vFile =
Repository.getDefault().getDefaultFileSystem().findResource(“myFolder/vFile”);

System.out.println(“the attr is:” + vFile.getAttribute(“attr1”));


程序将打印出”the attr is attrValue”。这是一个在System FileSystem中代表虚拟文件的FileObject的例子。当然,如果需要一个物理文件作为实体,也可以用“
url”属性建立虚拟文件和物理文件之间的映射。如例中的
<file name=”myFile.txt” url=”resource/realfile”/>。 用下面的代码,程
序将获得相对于layer.xml路径的”resource/realfile”物理文件,并且构造出File Object:




FileObject res =
Repository.getDefault().getDefaultFileSystem().findResource(“myFolder/myFile.txt”);


另外,在运行过程中,还可以在System FileSystem的根下,往myFolder目录下添加新的物理目录和文件:



FileObject myFolder =
Repository.getDefault().getDefaultFileSystem().getRoot().getFileObject(“myFolder”);
myFolder.createData(“newFile”);


程序将获得System FileSystem根下myFolder目录对应的FileObject,并调用createData方法,在这个目录下创建一个“newFile”文件。在
module的开发调试环境中这个“根“位于<myModulePath>\build\testuserdir\config,在以nbm的格式安装下,位
于<userdir>.netbeans\5.5\config目录下(在windows环境中,<userdir>常见于“C:\Documents and
Settings\<useraccount>”)。


更cool的是,在System FileSystem中,可以使FileObject成为java对象的工厂。在上例中以.instance为后缀的文件,就是这样的例子。我们在介绍Data
System API的时候会详细介绍。


可见,在netbeans中,文件系统以及文件对象扮演了多种角色,但是它们基于同一抽象的特性,使下面介绍的DataObject,Node等能广泛适用。


数据系统 (Data System)


DataObject是FileObject的wrapper。FileObject代表的是一个类似于文件的实体,而DataObject代表的则是文件内容的模型。文件类型是通过后缀名来标志的,一
般来说不同的文件类型对应于不同的DataObject的实现。DataLoader是创建DataObject的工厂类,如果程序需要支持新的文件类型,则应实现相应的DataLoader
和DataObject,并在module的manifest中注册,如:


Name: org/netbeans/modules/povray/PovDataLoader.class

OpenIDE-Module-Class: Loader


如果不需要支持新的文件类型,则不用和DataLoader打交道。只须用下面的方法获得某个fileobject的DataObject:




DataObject.find(someFileObject);


和DataObject交互的方式是通过getCookie()和getLookup()方法。这两个方法应该是可以互换的。getCookie()在将来的netbeans版本中,将
被getLookup所替代。注意,这里的Cookie和http协议中的Cookie没有关系。它代表的是这个DataObject能够提供的操作或者服务。用法如下:


OpenCookie open = someDataObject.getCookie (OpenCookie.class);



if (open != null) {

open.open();

}


如果在someDataObject上如果支持open操作,则从这个DataObject中获得实现了OpenCookie接口的对象,然后调用这个对象上的open操作。这样,DataObject
中并不实现具体的操作,接口实现代码封装在cookie中,通过getCookie和getLookup(关于Lookup,在后面介绍)的编程接口,提供给调用者。


前面提到,System FileSystem中,以.instance为后缀的文件构造的FileObject,能成为java对象的工厂。让我们看看是怎么实现的:在例中的lay.xml中,




//fo中包含了要构造的对象的类名。

FileObject fo =
Repository.getDefault().getDefaultFileSystem().findResource(“Actions/Edit/org-myorg-myfirstmodule-GoogleAction.instance”)



DataObject dobject = DataObject.find(fo); //根据后缀名,获得DataObject对象。

InstanceCookie ic = dobject.getCookie(InstanceCookie.class) //从DataObject中,获
得InstanceCookie的实现

if (ic != null) { //如果该DataObject支持创建新的实例的操作

  GoogleAction myAction = (GoogleAction)ic.createInstance(); //利用Cookie接口,创建Java对象

}


和以.instance为后缀名的情况类似,如果文件对象的后缀名是.ser,即对象的串行化的结果文件,也可以通过获得InstanceCookie的方法来创建被串行化的对象。


节点(Nodes)


前面提到,FileObject是文件的抽象,DataObject是文件内容的模型,而Node是表现层的对象。它们有actions(操作),properties(属性),本
地化的显示名称以及图标等等,封装了对象在人机接口方面的特性。需要注意的是,Netbeans 的Node不是GUI对象,和JDK中的TreeNode没有继承关系。但类似的是,它一般来说,也
是一个树型结构,每一个Node有Children对象,提供子节点的列表。


  • Node和Lookup

    和DataObject类似,每个Node对象都有一个Lookup对象,来存储上下文相关的信息。对于两者而言,调用者都可以通过getLookup方法获得 其上下文以及支持的操作。同
    样Node也有getCookie方法,但是它的典型实现是,代理给了DataObject的getCookie()方法。



    Node实例中的Lookup对象是如何设置的呢?这里列举几种方法:

    - 在API中,有可能用到Node的基本实现类AbstractNode(注意,它其实并不是一个抽象类), 在AbstractNode的构造方法中,可以以Lookup为参数。

    - 从DataObject中调用用getNodeDelegate()方法获得的Node对象,可能已经包含了Lookup。

    - 子类如从FilterNode继承,则子类的Lookup可以从代理Node对象中获得。

    - 子类中重载Node的getLookup方法,返回所需的Lookup对象



    至于Lookup的详细说明,以及怎么创建一个Lookup对象,见下章节。

  • Node和Explorer Views

    Explorer Views是界面对象,Node可以用不同的View展现出来。这些View对象包含在Explorer & Property Sheet API中,比
    如BeanTreeView用来显示Node 的树型结构,ListView用来显示Node的列表等。至于怎样用View去展现Node,会在Explorer API中介绍。

  • Node和DataObject

    DataObject可以用Node,也可以不用Node来表现。同样,Node后面的数据,可以是DataObject,也可以不是DataObject。但是,在某些场景下是互相关联的,它们
    之间也有API可以方便的互相转换。比如,希望用界面树型组件来显示磁盘上的某个目录结构,或者System FileSystem中的虚拟目录。很自然,从FileSystem,到 FileObject,D
    ataObject,Node,最后用Explorer View显示出来。



    现在我们从编程的角度,看看FileSystem,FileObject, DataObject, Node之间调用和转换关系:




    //Find a file on disk

    FileObject f =
    Repository.getDefault().getDefaultFilesystem().getRoot().getFileObject(“lsome/folder/someFile.txt”);



    //or if something passes you a File…

    FileObject f = FileUtil.toFileObject (new File(“some/folder/someFile.txt”));





    //Turn a FileObject into a File (may fail for virtual filesystems)

    File f = FileUtil.toFile (someFileObject)



    //Get the DataObject for a FileObject

    DataObject obj = DataObject.find (someFileObject)



    //Get the FileObject a DataObject represents

    FileObject file = someDataObject.getPrimaryFile();



    //Get the Node that represents a FileObject

    Node n = someDataObject.getNodeDelegate();



    //Get the DataObject a Node represents (if any)

    DataObject obj = (DataObject) someNode.getLookup().lookup(DataObject.class);

  • Node和Action

    Node可以提供一组Actions,这些Actions表示在该node被选中的时候,右键弹出菜单中将显示的项目。在这组Actions中,开
    发者可以加入一个AbstractAction的子类, 也可以从SystemAction中获得系统菜单中支持的如“delete”,“copy”,“save”等Action。如下例所示:




    public Action[] getActions(boolean popup) {

        DataFolder df = (DataFolder)getLookup().lookup(DataFolder.class);

        return new Action[] {

            new AddRssAction(df),

            SystemAction.get(DeleteAction.class),

            SystemAction.get(SaveAction.class)

        };




    当该Node被选中,在点击右键弹出的菜单中,会有三个选项,一个是“add”操作,操作的实现在AddRssAction中,该操作是在激活状态,因为AddRssAction就是这
    个action的实现;第二个是系统Action中的“delete”操作。操作如“delete”,“copy”,“paste”,“cut”,它们的实现可以由ExploreUtil的工厂方法来 提供,它
    们的激活与否是由ExploreManager以及当前的剪贴板等状态来控制的。第三个选项是系统Action中的“save”操作,它的激活与否在于该Node的Lookup或者
    cookie中有没有SaveCookie的实现。简而言之,getActions()返回了右键菜单中的选项,但是这些选项是否激活,取决于运行系统是否能以各种方式获得对应action的 实现。<
    /p>


Lookup


在DataObject和Node的介绍中,多次提到Lookup。Lookup(
http://www.netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/Lookup.html)类,是
被广泛应用于netbeans API中一个很重要的概念,我们也可以把它看成是Netbeans版本的IoC的实现,它也是各个模块之间实现decouple的关键所在。可以先简单的把它理解为,它
是一个Map,其中keys是class对象,对应每个key的value,是这个class的实例。一般来说,这个class对象是一个interface,定义了某服务的接口,而class实例,是
这个服务的实现。


在Netbeans中,有两种Lookup的用法。一种是用于从全局的服务登记表中获得某个服务的实现,Lookup.getDefault()调用返回的Lookup对象,即这个全局的服务登记表;另
一种,局部的用法,用来查询某个对象是否支持某个服务,并且获得它所支持的服务的实现。很多类型的对象都有自己的Lookup,比如TopComponent,DataObject,Node等等,可
以用类似于obj.getLookup()调用来获得obj的Lookup,这个Lookup对象里存的是当前上下文信息。对于调用者来说,只需获得它所关心的对象的Lookup,从中检索并调用相应的服务。&
amp; amp; amp; lt; /p>


这里我们主要探讨一下Lookup的局部用法。用户在IDE环境中的常用操作之一是,移动焦点,并且“选择”某个对象,在这个对象上执行相应操作。当前焦点所在的对象,也称当前选中的对象,活动的对象,或
者处于激活状态的对象。Netbeans IDE菜单中的常见功能,比如“打开”,是在当前所选中的对象上执行“打开”操作。显然对于不同的对象,“打开”操作的实现是不一样的。当选择不同的对象的时候,也
可见属性窗口中的属性值随着对象的不同而变化。那么在同样一个框架下,如何判断某个特定对象是否支持“打开”这样的公用操作,并且执行操作?当焦点对象变化的时候,框架如何得到通知,从而作相应的变化?在
Netbeans中,Lookup就是解决类似问题的答案。


例如下面这个例子就是从Node对象中获得它的Lookup:


Node[] n = TopComponent.getRegistry().getActivatedNodes();



if (n.length == 1) {

OpenCookie oc = (OpenCookie) n[0].getLookup().lookup(OpenCookie.class);

if (oc != null) {

oc.open();

}

}


这段代码,就可以用于实现上文描述的关于“打开”操作的场景,Lookup的应用,使得“打开”这个操作不需关心当前活动的节点的内容,也不用关心该节点将怎么执行 “打开”操作,只
要这个节点的Lookup中能够检索到OpenCookie接口的实现,调用其open函数即可。这样就实现了模块之间的解耦。关于Lookup的更细节的用法说明,请参见:


Lookup的基本概念:

http://openide.netbeans.org/lookup/



用Lookup SPI构造自己的Lookup:

http://www.netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/lookup/doc-files/lookup-spi.html



Lookup API的使用:

http://www.netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/lookup/doc-files/lookup-api.html

 


Window System


在Window System API中,最常用到的是TopComponent类,它是可嵌入式的窗口组件的父类,负责跟Netbeans的窗口系统交互。在
前面介绍Netbeans提供的代码模板类型中,就提到TopComponent。选择“Window Component”模板自动生成的java类,就是TopComponent的子类。Top
component可以是一个单独的窗口,也可以嵌入在tab页中。Netbeans的整个界面环境被划分为不同的部分,每个部分都有名称。比如:“explorer”,一般指位于界面左上角位置的窗口部分,“
Project”,“Files”,“Runtime”这些都是位于“explorer”位置的窗口组件。”Editor”,指的是位于界面中间的部分,比如各种编辑器都是位于这个部分。生成新的窗口组件时,会
要求指定窗口所处的位置。


每一个TopComponent实例都有一个Lookup的对象来标志其上下文,有一组Explorer view对象作为其界面元素,和一个或者多个激活的节点(Nodes)。程序中,一
般用associateLookup方法来设置TopComponent的Lookup对象,而当前激活的nodes则一般从下文将要介绍的ExplorerManager中得到。


Explorer & Explorer Views


Nodes提供了一组层次化的对象,而Explorer API则提供了界面对象(Explorer View)来显示这些Nodes。但是Nodes和界面对象view之间,并没有直接的调用关系,它
们之间的交互是通过ExplorerManager来实现的。用MVC的观点看,ExplorerManager就是Controller,它提供一个或者多个view要显示的node内容,管
理了views之间的共享的状态,如当前选中的节点和它的Lookup。当用户在某个view上的操作导致共享状态的改变时,该view对象会调用ExplorerManager的方法更新共享状态,E
xplorerManager再将共享状态的改变通知到其它的view。结合这些概念,来看一段代码:


class FeedTopComponent extends TopComponent implements ExplorerManager.Provider {

private final ExplorerManager manager = new ExplorerManager();

private final BeanTreeView view = new BeanTreeView();

private FeedTopComponent() {

add(view, BorderLayout.CENTER);

view.setRootVisible(true);

try {

manager.setRootContext(new RssNode.RootRssNode());

} catch (DataObjectNotFoundException ex) {

ErrorManager.getDefault().notify(ex);

}

ActionMap map = getActionMap();

map.put(“delete”, ExplorerUtils.actionDelete(manager, true));

associateLookup(ExplorerUtils.createLookup(manager, map));

}



public ExplorerManager getExplorerManager() {

return manager;

}

………..

}




示例中,FeedTopComponent即上文提到的TopComponent的子类,BeanTreeView作为一个Explorer View对象加入到了窗口对象中。B
eanTreeView需要显示的节点,以及共享状态是由ExplorerManager来控制的。通过调用ExploreManager的setRootContext方法,设置了这组节点的根。


因为ExplorerManager管理了nodes的状态,所以前面提到能从TopComponent窗口实例中获得一组当前激活的节点,实际上,是从其内置的ExplorerManager 中获得。T
opComponent的Lookup对象用ExplorerUtils.createLookup(ExplorerManager,ActionMap)方法创建,使
得TopComponent的Lookup实际上,代理给了由ExplorerManager管理下的当前活动节点的Lookup和ActionMap中支持的action操作。


ActionMap对象维护了action的名字和对应的action实现的映射。示例中,ActionMap添加了“delete”操作,通
过把ActionMap和ExplorerManager一起加入到TopComponent的lookup中,使“delete”操作能够在ExplorerManager管理下的nodes上执行。也
就是说当某个node选中的时候,系统的“delete”菜单会被激活。


其它的API


Netbeans中还有很多别的API,可以参见Netbeans的网站上javadoc和相关资料。当然关于API的了解,往往也是在实际运用中得到增强的。


总结


本文首先介绍了创建Netbeans Plugin Module的一般步骤,然后就开发过程中要涉及到的基本概念做了探讨,目的在于了解Netbeans Plugin
Module开发中的基本思想和要素,为更深入的学习和理解打下一定的基础。


参考文档



http://platform.netbeans.org/tutorials/index.html



http://www.netbeans.org/download/dev/javadoc/