NetBeans选择管理教程 I—使用TopComponent的Lookup

来源:互联网 发布:Linux的loop 编辑:程序博客网 时间:2024/05/17 11:35

本教程介绍如何编写提供选定对象的组件以及可随全局选择的更改而进行自我更新的组件。

选择是所有非平凡应用程序的一个重要概念。NetBeans有两个关于选择的基本概念获得焦点的TopComponentLookup以及获得焦点的TopComponentactivated Node(s)本教程将只介绍选择的查找部分在未来教程中将介绍更高级的内容。

使用选择可以执行诸如上下文敏感之类的操作(根据所显示的内容启用或禁用的操作),并且可以使IDE中的每个调色板窗口(如Property SheetNavigator组件)显示所选内容的某个方面。

基本上每个TopComponent都有一个对象包该包可以封装操作并且其他代码能够查询它。这个对象包就是它的Lookup实际上就是一个Map,其中的键就是类对象,而值就是扩展或实现键-类的对象。该方法的一大优点就是能够使用该机制对提供某些对象的组件以及使用这些对象的组件进行解耦以便它们可以在单独的模块中实现,或者可以为旧对象提供新的编辑器,而系统的其余部分将继续透明地工作。

若要下载完整的示例请访问http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=3146

创建模块套件和项目

本教程的示例将包含三个模块均包含在一个模块套件中如下图所示。

首先创建模块套件以包含所有三个模块

1.       选择File > New Project (Ctrl-Shift-N)。在Categories选择NetBeans Plug-in Modules。在Projects选择Module Suite Project并单击Next

2.       NameLocation面板中Project Name中键入SelectionSuite。将Project Location更改为计算机上的任意目录。单击Finish

3.       再次选择File > New Project (Ctrl-Shift-N)。在Categories选择NetBeans Plug-in ModulesProjects选择Module Project并单击Next

4.       NameLocation面板中Project Name中键入MyAPI向导中的默认值用于在您刚刚创建套件的目录下创建该模块此处可以保留。单击Next

5.       Basic Module Configuration面板中Code Name Base中的 yourorg替换为myorg以便代码名称库为org.myorg.myapi。向默认的Module Display Name中添加空格以便它更改为My API保留本地化包和XML层的位置,以便它们将存储在名称为org/myorg/myapi的包中。单击Finish

6.       现在您将创建两个以上的模块按照上面的相同步骤但使用名称"MyEditor""MyViewer"随着进一步操作,您将了解创建三个模块的原因。

创建API并设置依赖性

此处您将要执行的操作就是创建一个普通的API类。在现实世界中,这样一个API可能代表一些文件或通过编程方式建模的某些其他种类的数据。出于本教程的目的,只需让简单对象拥有几个属性就够了。

1.         右键单击org.myorg.myapi 包并选择New > Java Class如下所示

2.       将该类命名为APIObject

3.       将默认代码替换为以下内容:

public final class APIObject {
 
private final Date date = new Date();
private static int count = 0;
private final int index;
 
public APIObject() {
index = count++;
   }
 
public Date getDate() {
return date;
   }
 
public int getIndex() {
return index;
   }
   
public String toString() {
return index + " - " + date;
   }
   
}

这将是该模块包含的全部代码。正如您所见每次创建APIObject的新实例时都会使计数器增加以便APIObject的每个实例都用一些独特的属性。

4.         下一步是让API模块导出org.myorg.myapi包以便其他包可以看到其中的类。右键单击My API项目并选择Properties

5.         Project Properties对话框的API Versioning页面中Public Packages列表中选中org.myorg.api的复选框如下所示。

6.         现在您需要在模块之间设置一些依赖性。其他两个模块My EditorMy Viewer将使用APIObject类,以便它们中的每个模块需要声明它们依赖于API模块。对于每个其他模块项目右键单击项目节点并选择 Properties

7.         在每个Project Properties对话框的Libraries页面中单击Add Dependency按钮。在弹出的对话框中键入APIObject应该有惟一一个匹配那就是您的API模块。选择该模块并单击OK以添加依赖性。

创建Viewer Component

现在您将创建一个单独组件该组件将跟踪全局选择中是否存在可用的APIObject获得焦点的TopComponent是否在其Lookup中拥有一个APIObject如果存在一个APIObject,它将显示有关它的一些数据。对于此类事项的一个常见用例就是创建主/详细信息视图。

单独组件就是类似于NetBeans IDE中的Projects窗口、Property SheetNavigator组件系统中永远存在的一个惟一组件。Window Component向导将自动生成创建此类单独组件所需的所有代码您只需使用表单设计器或编写代码来提供单独组件的内容。

  1. 右键单击org.myorg.myviewer并选择New > Other
  2. 在显示的对话框中选择Module Development > Window Component并单击Next或按Enter
  3. 在向导的Basic Settings页面上选择navigator作为放置您的查看器组件的位置并且选中在启动时打开该组件的复选框如下所示

  1. 单击Next进入向导的Name, Icon and Location页面。
  2. 在下面的页面中,将类命名为MyViewer并单击Finish或按Enter

现在您拥有一个TopComponent骨架称为MyViewerTopComponent的单独组件。单击MyViewerTopComponent的编辑器选项卡应该可以看到表单编辑器。向该组件中添加两个标签,它们将显示所选择的APIObject(如果有的话)的一些信息。

1.         将两个JLabelsPalette拖动到表单一个标签位于另一个标签的下面。

如上面所示更改第一个标签的文本以便默认情况下它显示[nothing selected]

2.         单击编辑器工具栏中的Source按钮以切换到代码编辑器

3.         修改该类的签名以便MyViewerTopComponent实现LookupListener

public class MyViewerTopComponent extends TopComponent implements LookupListener {

4.         右键单击编辑器并选择Fix Imports以便导入LookupListener

5.         将签名行中放置插入符号(^)如下所示。一个灯泡形状的图案将出现在编辑器边缘。按Alt-Enter,然后当出现带有文本“Implement All Abstract Methods”的弹出框时再次按Enter。这样会将LookupListener方法添加到您的类中。

6.         您现在拥有一个实现LookupListener的类。现在它需要侦听某些事项。本例中,有一个方便的全局Lookup对象,该对象只是代理获得焦点的组件的Lookup—可以通过调用Utilities.actionsGlobalContext()来获得。因此,您可以只侦听这个全局选择查找,而不必跟踪您自己获得焦点的组件,这样,焦点发生更改时就会激发相应的更改。编辑源代码,以便它包含以下方法,如下所示:

private Lookup.Result result = null;
public void componentOpened() {
Lookup.Template tpl = new Lookup.Template (APIObject.class);
result = Utilities.actionsGlobalContext().lookup(tpl);
result.addLookupListener (this);
    }
    
public void componentClosed() {
result.removeLookupListener (this);
result = null;
    }
    
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection c = r.allInstances();
if (!c.isEmpty()) {
APIObject o = (APIObject) c.iterator().next();
jLabel1.setText (Integer.toString(o.getIndex()));
jLabel2.setText (o.getDate().toString());
} else {
jLabel1.setText("[no selection]");
jLabel2.setText ("");
        }
    }

只要在窗口系统中显示组件就调用componentOpened()只要用户单击其选项卡上的X按钮关闭组件就调用componentClosed()因此当显示组件时,您希望对它跟踪选择上面代码所执行的操作。

resultChanged()方法是LookupListener的实现。只要选定的APIObject更改它就会更新您放在表单上的两个JLabel

创建Editor Component

现在您需要某些事项来实际提供APIObject的实例以便使用该代码。幸运的是,这非常简单。

这次您将创建另一个TopComponent它在编辑器区域打开并从其Lookup提供APIObject的一个实例。可以再次使用Window Component模板,但该模板设计用于创建单独组件,而不是很多组件。因此您将只需创建一个没有模板的TopComponent子类,以及一个将打开其他子类的操作。

  1. 您将需要向My Editor模板中添加三个依赖性以便它能够查找您将使用的类。右键单击My Editor项目并选择PropertiesProject Properties对话框的Library页面上,单击Add Dependency按钮,并键入TopComponent该对话框将自动建议设置对Window System API的依赖性。Lookups (Utilities API)执行相同的操作。
  2. 右键单击My Editor项目中的org.myorg.myeditor包并选择New > JPanel Form
  3. 将它命名为“MyEditor”,并完成该向导。
  4. 当打开表单编辑器时将两个JTextField拖动到表单上一个在另一个的上面。在属性页上,将每个JTextFieldEditable属性(复选框)设置为false
  5. 单击编辑器工具栏中的Source按钮以切换到代码编辑器。
  6. 更改MyEditor的签名以展开TopComponent而不是javax.swing.JPanel
public class MyEditor extends TopComponent {
  1. 将以下代码添加到MyEditor的构造函数
APIObject obj = new APIObject();
associateLookup (Lookups.singleton (obj));
jTextField1.setText ("APIObject #" + obj.getIndex());
jTextField2.setText ("Created:" + obj.getDate());
setDisplayName ("MyEditor " + obj.getIndex());

右键单击编辑器并选择Fix Imports

associateLookup (Lookups.singleton (obj));将创建一个只包含一个对象的Lookup—APIObject的新实例并将该Lookup指定为由MyEditor.getLookup()返回的内容。尽管这是一个虚构的示例,但是您可以想像APIObject如何代表一个文件、数据库中的一个条目或者您希望编辑或查看的其他任何内容。您还可以设想一个组件,该组件允许您选择或编辑APIObject多个惟一实例这将是下一部教程的主题。

为了使您的编辑器组件变得有趣一些尽管它实际上并不编辑任何内容),您将文本字段的值设置为APIObject中的值以便有内容可显示。

打开Editor Components

现在您需要一种在编辑器区域打开MyEditor组件的方法以便显示某些内容。若要通过选择执行有意义的操作,您需要多个编辑器,以便对多个APIObject跟踪。由于您希望有多个编辑器因此需要主菜单具有一个简单操作该操作将创建并在窗口系统中打开MyEditor的另一个实例相对于Window Component模板为我们创建的内容即始终查找单独组件的操作IDE中的NavigatorProperty Sheet组件

  1. 右键单击org.myorg.myeditor并选择New > Other
  2. 在对话框中选择Module Development > Action并单击Next
  3. 接受默认值(“always enabled”)并再次按Next
  4. GUI Registration页上通过再次按Next接受默认值这将使您的操作出现在File菜单顶部
  5. 在向导的最后一页上将该操作命名为OpenEditorAction并将其显示名称设置为Open Editor
  6. Finish以生成该操作类。
  7. 现在代码编辑器应该通过一个名为OpenEditorAction的类打开该类创建CallableSystemAction的子类javax.swing.ActionNetBeans子类javax.swing.Action允许您将上下文敏感的帮助与某个操作相关联向其performAction()方法中添加以下代码:
{0>MyEditor editor = new MyEditor();<}0{>MyEditor editor = new MyEditor();
editor.open();
editor.requestActive();

以上代码将只创建MyEditor的一个新实例这又会创建APIObject的一个新实例并将该实例放在其Lookup并在窗口系统中打开该实例。

运行代码

现在您准备运行该教程。只需右键单击拥有三个模块的模块套件SelectionSuite并从弹出菜单中选择RunIDE打开时,只需选择File > Open Editor—调用您的操作。反复执行这些操作,以便打开多个编辑器组件。还应该打开单独的MyViewer窗口。请注意,在单击不同选项卡时MyViewer窗口的内容如何随之改变,如下所示:

如果您单击Projects窗口则会注意到文本更改为[No Selection]”,如下所示

如果您没有看到MyViewer窗口那么可能是因为没有在向导中选中表示在系统启动时打开的复选框只需转到Window菜单并选择Open MyViewer Window以显示它。

那么,重点是什么?

您可能想了解这次练习的意义只是向您显示我们能够处理选择这很重要!关键是将代码分成了三个模块:My Viewer模块不知道有关My Editor模块(每个模块都可以独自运行)的任何内容。它们仅共享对My API的常见依赖性。这非常重要它意味着两件事情1. 可以单独开发和发布My ViewerMy Editor2. 想提供不同于My Editor的另一种编辑器的任何模块都可以这样做并且只要替代编辑器从其Lookup中提供APIObject的一个实例查看器组件就会完美地配合工作。

为了实际描绘该操作的价值假设APIObject是更复杂的内容,比如MyEditor是一个图像编辑器并且APIObject代表正在编辑的图像。此处可以实现的强大功能是您能够将MyEditor替换为基于SVG矢量的编辑器(假设),并且查看器组件(假设显示当前编辑图像的属性)将与此新的编辑器透明工作。这就是工作模型,因此您可以向针对Java文件的NetBeans IDE添加新的工具,这些工具将在不同版本的NetBeans中工作,您可以让Java文件拥有另一个编辑器(如表单编辑器),并且当使用表单编辑器时,针对Java文件的所有组件和操作仍将正常工作。

这就是NetBeans处理Java其他源文件的方式这种情况下可以从编辑器的Lookup中获得的内容为DataObject组件NavigatorProperty Sheet只是查看获得焦点的TopComponent可用对象。

这种方法的另一个要点是人们经常将现有应用程序迁移到NetBeans平台。这种情况下,属于数据模型的对象可能就是现有的工作代码,为了使该代码集成到NetBeans中,不应该对代码进行更改。通过将数据模型的API保留在单独的模块中,可以保持NetBeans集成与核心业务逻辑分离。

动态更改选定对象

为了更清晰地证明这个方法的强大功能您还需要采取一个步骤向编辑器组件添加一个按钮,使用新的APIObject动态替换已有的APIObject

  1. 在表单编辑器中打开MyEditor单击编辑器工具栏中的Design工具栏按钮),然后将一个JButton拖动到其中。
  2. JButton的文本属性设置为“Replace”。
  3. 右键单击JButton然后选择Events > Action > actionPerformed。这将打开代码编辑器并且事件处理程序方法中有插入符号。
  4. 在类定义的前面您将添加最后一个字段
public class MyEditor extends TopComponent {
private final InstanceContent content = new InstanceContent();

InstanceContent是一个类该类允许我们动态修改Lookup的内容(具体指AbstractLookup的一个实例

  1. 将以前添加到构造函数的所有行复制到剪贴板然后将其从构造函数中删除但以associateLookup...开头的行除外。该构造函数行应该更改为以下形式:
associateLookup (new AbstractLookup (content)); 
  1. 您将在JButton的操作处理程序中使用放置在剪贴板中的行因此您应该在第一次初始化该组件时运行一次该代码。向构造函数添加以下行,位于上面的行之后:
jButton1ActionPerformed (null);
  1. 通过粘贴剪贴板内容并将以下行添加到末尾修改事件处理程序方法,使它如下面所示
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
APIObject obj = new APIObject();
jTextField1.setText ("APIObject #" + obj.getIndex());
jTextField2.setText ("Created:" + obj.getDate());
setDisplayName ("MyEditor " + obj.getIndex());
content.set(Collections.singleton (obj), null);
}
  1. 右键单击编辑器并选择Fix Imports

您现在准备再次运行该套件。再次右键单击SelectionSuite并选择Run注意,现在当您单击Replace按钮时,所有组件包括MyViewer实例等所有内容是如何进行更新的。

提供多个对象

这当然适合于解耦是不是从组件中提供这一个对象类似于拥有一个只包含一个键和一个值的Map答案是肯定的。当您从多个API中提供多个对象时,该技术就会变得更强大。

例如NetBeans中提供上下文敏感的操作是非常常见的。一个恰当的例子就是构成NetBeansActions API的内置SaveAction。该操作实际所执行的内容是,只侦听在全局上下文上是否存在名为SaveCookie的内容方法与的查看器窗口侦听APIObject一样。如果出现一个SaveCookie(通常当文件的内容被修改但尚未保存时编辑器会向其查找中添加一个SaveCookie),则该操作变为激活状态,因此Save工具栏按钮和菜单项也变为激活状态。当调用Save操作时它调用SaveCookie.save()从而使SaveCookie消失因此Save操作变为禁用直到出现一个新的SaveCookie为止。

您可能已经注意到上下文敏感是New Action向导的一个选项。该向导当前生成的操作实际使用的方法在Lookup之前执行执行此类上下文敏感操作的基于Lookup的方法如developer FAQ中所述。

因此实际的模式是从您的组件的Lookup中提供多个对象对于正在编辑的对象,不同的辅助组件和不同的操作所感兴趣的方面也不一样。可以将这些方面清晰地划分为辅助组件和操作可以依赖和侦听的接口。

值得注意的其他事项

尽管与本教程主题不直接相关但是值得注意的是如果您打开三个MyEditor实例然后关闭并重新启动NetBeans您将会看到重新启动后将神奇地出现三个MyEditor实例。默认情况下,您的编辑器在关闭时已序列化到磁盘上并在重新启动时还原。

如果您不希望出现此行为则有两个其他选择。MyEditor上覆盖以下方法以使编辑器不会在重新启动时重新打开:

public int getPersistenceType() {
return PERSISTENCE_NEVER;
}

如果您希望持久保存打开的组件而丢弃已经关闭的那些组件则返回PERSISTENCE_ONLY_OPENED。默认设置(出于向后兼容的原因)为PERSISTENCE_ALWAYS,该设置并不适合于编辑器样式的组件这意味着即使已经关闭的编辑器也会永远保留并在重新启动时重新加载。

但是请注意已序列化到磁盘中的部分内容是您的组件在主窗口中的位置因此单独的TopComponent(如属性页或我们的查看器组件)应该使用PERSISTENCE_ALWAYS否则如果用户关闭它们后,下次打开时它们将出现在编辑器区域中,而不是出现在应该出现的位置。

进行清理

默认情况下模块模板假设您想使用layer.xml文件安装对象。如果是My API模块,则实际上并不使用该文件。因此要稍微缩短启动时间,应执行下面的方法:

  1. 展开My API项目的Important Files节点
  2. 双击Module Manifest节点
  3. Manifest中删除以下行:
OpenIDE-Module-Layer:org/myorg/myapi/layer.xml
  1. 然后删除org.myorg.myapi中相应的layer.xml文件

下一步

到目前为止已经注意到某些组件具有更加粒度化的选择逻辑甚至涉及多个选择。在下一部教程中,将介绍如何使用Nodes API处理这种情况。